{"id":100405,"date":"2018-12-05T07:00:00","date_gmt":"2018-12-05T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=100405"},"modified":"2019-03-13T00:10:45","modified_gmt":"2019-03-13T07:10:45","slug":"20181205-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20181205-00\/?p=100405","title":{"rendered":"How can dereferencing the first character of a string take longer when the string is longer? I&#8217;m looking only at the first character, which should be constant time"},"content":{"rendered":"<p>Consider this program. <\/p>\n<pre>\nchar* malloc_random_string_length(int length)\n{\n char* s = (char*)malloc(length + 1);\n for (int i = 0; i &lt; length; i++) {\n  s[i] = '\\0' + (rand() % 10);\n }\n s[length] = '\\0';\n return s;\n}\n\nint test()\n{\n char* array1[NUMBER_OF_STRINGS];\n char* array2[NUMBER_OF_STRINGS];\n int i;\n int matches = 0;\n\n for (i = 0; i &lt; NUMBER_OF_STRINGS; i++) {\n  array1[i] = malloc_random_string_length(STRING_LENGTH);\n  array2[i] = malloc_random_string_length(STRING_LENGTH);\n }\n\n \/\/ Okay, now time this loop\n start_stopwatch();\n matches = 0;\n for (i = 0; i &lt; NUMBER_OF_STRINGS; i++) {\n  if (compare_in_some_way(array1[i], array2[i])) {\n   matches++;\n  }\n }\n stop_stopwatch();\n\n \/\/ Return this value so the compiler won't optimize it out\n return matches;\n}\n<\/pre>\n<p>This code creates two arrays, each with <code>NUMBER_<code><\/code>OF_<code><\/code>STRINGS<\/code> random strings, each of length <code>STRING_<code><\/code>LENGTH<\/code>. It then calls <code>compare_<code><\/code>in_<code><\/code>some_<code><\/code>way<\/code> on each pair of strings tallies how many of them pass the test. <\/p>\n<p>Consider this comparison function: <\/p>\n<pre>\nint compare_in_some_way(char* a, char* b)\n{\n return a == b; \/\/ just compare the raw pointers\n}\n<\/pre>\n<p>When run with various values for each with <code>NUMBER_<code><\/code>OF_<code><\/code>STRINGS<\/code> and <code>STRING_<code><\/code>LENGTH<\/code>, this code&#8217;s running time is proportional to <code>NUMBER_<code><\/code>OF_<code><\/code>STRINGS<\/code>, and the <code>STRING_<code><\/code>LENGTH<\/code> doesn&#8217;t play a role. <\/p>\n<p>On the other hand, consider this alternate comparison function: <\/p>\n<pre>\nint compare_in_some_way(char* a, char* b)\n{\n return *a == *b; \/\/ compare the first characters\n}\n<\/pre>\n<p>This compares the first characters of the strings. With this version, it naturally runs slower as <code>NUMBER_<code><\/code>OF_<code><\/code>STRINGS<\/code> increases, but surprisingly, it also runs slower as <code>STRING_<code><\/code>LENGTH<\/code> increases. <\/p>\n<p>How can the length of the string play a factor in how long it takes to compare the first character? The function doesn&#8217;t even know what the length of the string is! <\/p>\n<p>What we&#8217;re seeing is the effect of data locality. <\/p>\n<p>In the first version that compares only pointer values, the only memory accesses are to the memory for the arrays themselves. The data in those arrays are tightly packed, so the cache is used efficiently. Since you don&#8217;t dereference the pointers, it doesn&#8217;t matter where they point. <\/p>\n<p>Reading the first character from the string adds another memory access, and the characteristics of that memory access vary depending on the length of the string. <\/p>\n<p>From the experimental data, one can conclude that the string data is stored in roughly contiguous memory. When the strings are short, the first characters of each string are closer to each other, since there are fewer other characters in between. This means that they are more likely to occupy the same cache line. <\/p>\n<p>As the strings get longer, the distance between their first characters increases, and fewer strings will fit inside a cache line. Eventually, the strings are long enough that each string ends up on a separate cache line, and you don&#8217;t gain any significant benefit from locality. <\/p>\n<p>Although the access to the first character of the string is always <var>O<\/var>(1), the constant inside the <var>O<\/var> can vary wildly depending on cache conditions. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>There&#8217;s a lot hiding in that <VAR>O<\/VAR>.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-100405","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>There&#8217;s a lot hiding in that <VAR>O<\/VAR>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=100405"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100405\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=100405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=100405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=100405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}