The Surprising Cost of Type Checking in Eiffel
- Contents
- Introduction
- Benchmarks
- Conclusion
Introduction
If I asked you the question: given identical arguments of type EL_ZSTRING
, which of the append
routines in the code fragment below is faster, we would of course answer append_string
. The reason is that in the case of append_string_general
we can assume a small amount of overhead to do the two type checks.
- check if
general
conforms toREADABLE_STRING_8
- check if
general
conforms toZSTRING
However if I asked you to guess how much faster append_string
is than append_string_general
, I think we are likely to get a lot of wrong answers. Despite programming in Eiffel since 2000, I was surprised to discover that these two type checks add significant computational overhead. See benchmarks below.
deferred class
EL_APPENDABLE_ZSTRING
inherit
EL_ZSTRING_IMPLEMENTATION
feature {EL_READABLE_ZSTRING, STRING_HANDLER} -- Append strings
append_string, append (s: EL_READABLE_ZSTRING)
local
old_count: INTEGER
do
old_count := count
internal_append (s, old_count)
if s.has_mixed_encoding then
append_unencoded (s, old_count)
end
ensure
new_count: count = old count + s.count
inserted: elks_checking implies same_string (old (current_readable + s))
end
append_string_general (general: READABLE_STRING_GENERAL)
local
offset: INTEGER
do
if attached {READABLE_STRING_8} general as str_8 and then cursor_8 (str_8).all_ascii then
append_ascii (str_8)
elseif attached {EL_ZSTRING} general as str_z then
append_string (str_z)
else
offset := count
accommodate (general.count)
encode (general, offset)
end
ensure
unencoded_valid: is_valid
appended: substring (old count + 1, count).same_string_general (general)
end
Benchmarks
I use the following code to benchmark the routines. The test data in Hexagram.English_titles consists of 64 strings with on average 62 characters each. Scroll down to see the results.
class
APPEND_GENERAL_VS_APPEND
inherit
STRING_BENCHMARK_COMPARISON
EL_SHARED_ZSTRING_CODEC
create
make
feature -- Access
Description: STRING = "{ZSTRING}.append_general VS append"
feature -- Basic operations
execute
local
list: EL_ZSTRING_LIST
do
create list.make_from_general (Hexagram.English_titles)
compare ("compare append_general VS append", <<
["append_general", agent append_general (list)],
["append", agent append (list)]
>>)
end
feature {NONE} -- append
append (string_list: EL_ZSTRING_LIST)
local
str: ZSTRING
do
create str.make (string_list.character_count)
across string_list as list loop
str.append (list.item)
end
end
append_general (string_list: EL_ZSTRING_LIST)
local
str: ZSTRING
do
create str.make (string_list.character_count)
across string_list as list loop
str.append_string_general (list.item)
end
end
end
Console Output
RESULTS: compare append_general VS append
Passes over 500 millisecs (in descending order)
append : 28112.0 times (100%)
append_general : 20264.0 times (-27.9%)
Conclusion
Does a 28% reduction in performance seem like a lot to you? It does to me. I am assuming that it is the type checking that is slowing things down, and not the nested call to `append_string'. Or is there something I am missing ?
I will now think twice about doing type checking for performance critical routines.