The “no pointers” thing is cache efficient for three reasons:
1. Indices can be smaller than pointers.
2. The data is packed in memory. This means that cachelines will be full of relevant data, and adjacent cache lines (which are a bit faster than faraway lines) get used.
3. SOA form is more or less cache efficient depending on what you’re doing with it.
And that’s it.
If you need child pointers, use child pointers. If you want a cache-efficient tree, use a cache-efficient tree, e.g. a B-tree or an appropriate variant.
But those operations are horrible operations. Until you are log_2(cache line size / element size) steps from the leaves, you are hitting a different cache line each time, and you’re doing a branch such that the next cache line you want depends on the branch result.
So this is like log_2(n)-3 serial (cache miss, compare, branch, compute address operations). (Assume 8-byte entries — it’s much worse with bigger entries.) This not even close to optimal.
You will find that, for reasonably large data sets (maybe a few hundred elements or more?), any cache-optimized searchable data structure will beat binary search by a large factor. And for small arrays (or the last stage of an optimized large structure), you want a vectorized linear search.
Binary search is mostly nice because sorted arrays are elegant and binary search can be implemented in very little code. And it’s easy to explain and analyze.
But naive binary search can also be improved upon by dividing the searched space into 3 subranges instead of 2[2].
[1]: <https://www.pvk.ca/Blog/2012/07/03/binary-search-star-elimin...>
[2]: <https://www.pvk.ca/Blog/2012/07/30/binary-search-is-a-pathol...>