TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Fastest branchless binary search

338 pointsby jorangreefalmost 2 years ago

29 comments

3cats-in-a-coatalmost 2 years ago
Every time I see people trying to eliminate branches, I wonder, do we realize that having long pipelines where a branch misprediction stalls the pipeline is not actually a necessary part of architecture?<p>The pipeline is long because we do lots of analysis and translation on the fly, just in time, which could easily be done in most cases ahead of time, as it&#x27;s not a very stateful algorithm.<p>This is how Transmeta Crusoe CPUs worked. Imagine NOT caring that you have a branch.<p>After all, if you think about it, all operations are branches. Down to bitwise operations and summing two numbers. It&#x27;s branching in the ALU, carry bits and what not. You can&#x27;t compute absolutely anything without looking at the state of one or more bits, and making a decision that changes the outcome. But the reason those branches don&#x27;t hurt performance is that they&#x27;re not branches on the main pipeline. These local branches have a very short (or non-existent) &quot;pipeline&quot;. And the main pipeline is therefore not affected, despite the actual state of the system is.
评论 #37090995 未加载
评论 #37093078 未加载
评论 #37089800 未加载
评论 #37089566 未加载
评论 #37091048 未加载
评论 #37089644 未加载
评论 #37092447 未加载
评论 #37092145 未加载
shmageggyalmost 2 years ago
&gt; <i>If only there was a clean fast bare-metal language to write all this in..</i><p>The author includes a footnotes for &quot;BUT RUST..&quot; and &quot;BUT ZIG..&quot;, but how about Nim? Looks like there is a native library implementation of `lowerBound` <a href="https:&#x2F;&#x2F;github.com&#x2F;nim-lang&#x2F;Nim&#x2F;blob&#x2F;version-2-0&#x2F;lib&#x2F;pure&#x2F;algorithm.nim#L221">https:&#x2F;&#x2F;github.com&#x2F;nim-lang&#x2F;Nim&#x2F;blob&#x2F;version-2-0&#x2F;lib&#x2F;pure&#x2F;al...</a> Yes, it&#x27;s not a &quot;bare-metal&quot; language, but it compiles to one (or two), so it would be interesting to see what it compiles to here.<p>Also, what&#x27;s wrong with C?
评论 #37087079 未加载
评论 #37088810 未加载
评论 #37088246 未加载
w0mbatalmost 2 years ago
Is that still lower_bound? Maybe I am misreading the code but it looks like this returns any match, not the earliest match (when there are dupes).<p>It’s common to have multiple matches even in a unique list if the comparison function is say looking for a certain string prefix to do autocomplete, but we want the earliest in the list.
评论 #37089400 未加载
评论 #37088318 未加载
评论 #37092693 未加载
hajricealmost 2 years ago
I wish all blog posts started the way his does: &quot;You’re a busy person so I’ll first jump right to it. Here it is, the fastest general (and simple) binary search C++ implementation:&quot;
评论 #37088773 未加载
anonymoushnalmost 2 years ago
The Zig stdlib does not call out to C++ for binary search. The binary search is currently here: <a href="https:&#x2F;&#x2F;github.com&#x2F;ziglang&#x2F;zig&#x2F;blob&#x2F;b835fd90cef1447904d3b009c9662ba4c0ea77d4&#x2F;lib&#x2F;std&#x2F;sort.zig#L400">https:&#x2F;&#x2F;github.com&#x2F;ziglang&#x2F;zig&#x2F;blob&#x2F;b835fd90cef1447904d3b009...</a><p>(edit: fixed link to not decay, thanks)
评论 #37087257 未加载
评论 #37093969 未加载
alain94040almost 2 years ago
I don&#x27;t get it. The problem with binary search and branches is not the branches themselves, it&#x27;s the fact that until you have done the comparison, you don&#x27;t know which memory location in the array to fetch next. It doesn&#x27;t matter if you use branches or anything else, the question is what do you want the processor to do?<p>There is a data dependency: until I read the middle index, I can&#x27;t tell if I want to search the data in the upper section or the lower section. I can speculate and issue reads to both. That would fix the dependency, but create more memory traffic. Is this the right trade-off?<p>What do <i>you</i> want? Removing branches is not it.
评论 #37093387 未加载
评论 #37100595 未加载
评论 #37091906 未加载
bjournealmost 2 years ago
On my Cascade Lake processor &quot;-mllvm -x86-cmov-converter=false&quot; almost halves the performance of the binary search:<p><pre><code> | Benchmark | gcc | clang | clang -cmov | |-----------|------|-------|-------------| | slow u32 | 23.4 | 46.7 | 45.8 | | fast u32 | 18.1 | 19.8 | 31.4 | </code></pre> The numbers are nanoseconds&#x2F;bsearch on a 100mb uint32 array. Seem to me that clang (15.0.7) is just much worse at optimizing this particular piece of code than gcc (13.2.1). You can see the assembly here <a href="https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;cbx5Kdjs6" rel="nofollow noreferrer">https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;cbx5Kdjs6</a>. The gcc assembly looks way cleaner to me.
评论 #37094085 未加载
LoganDarkalmost 2 years ago
Does anyone know where the &quot;BUT RUST&quot; link was <i>supposed</i> to lead? It seems to be already out of date due to being unversioned, I can&#x27;t tell whether it&#x27;s supposed to lead to the middle of the `starts_with` doc comment or not.
评论 #37087281 未加载
评论 #37093318 未加载
djoldmanalmost 2 years ago
Interesting that the results don&#x27;t hold up with a more complicated comp comparison function:<p>&gt; For somewhat realistic scenarios of binary searching with a slower comp() function I’ve thought of searching through ids, phone numbers, accounts and keywords. I’ve thus settled on testing searching 8-byte strings.<p>&gt; ...<p>&gt; In this case std::lower_bound is very slightly but consistently faster than sb_lower_bound. To always get the best performance it is possible for libraries to use sb_lower_bound whenever directly working on primitive types and std::lower_bound otherwise.<p>I would like to see the analysis here.
评论 #37087523 未加载
评论 #37095384 未加载
评论 #37087295 未加载
funkychickenalmost 2 years ago
It looks like the unpredictable attribute now influences the cmov conversion pass (as of June 1st, so probably in clang 17&#x2F;18)<p><a href="https:&#x2F;&#x2F;reviews.llvm.org&#x2F;D118118" rel="nofollow noreferrer">https:&#x2F;&#x2F;reviews.llvm.org&#x2F;D118118</a>
foldralmost 2 years ago
&gt;gcc has __builtin_expect_with_probability(cond, 0, 0.5) but it does nothing (tested v10). ↩<p>I wonder what could possibly be the use of this builtin. Branch prediction varies enough between different processors that it seems unlikely that anything useful could be done with a fine-grained probability estimate.
评论 #37087065 未加载
评论 #37087114 未加载
评论 #37087442 未加载
评论 #37090877 未加载
评论 #37088334 未加载
andyg_blogalmost 2 years ago
This is not a valid drop in replacement for lower_bound. Accessing iterators as front[index] is only for random access iterators like vector has. The author may have realized this if they benchmarked on other containers.A forward iterator must be advanced and then dereferenced.
评论 #37088200 未加载
评论 #37088639 未加载
评论 #37094607 未加载
ectosphenoalmost 2 years ago
Every post like this makes me update my junior engineer watchlist. Then I have to go patiently explain why mucking about in the code to save one instruction because you read a blog post is a horrible idea. Easily half of all silly junior code is pointless optimization.<p>Having said that, I did enjoy the post.
评论 #37088846 未加载
amlutoalmost 2 years ago
Oh, C++, the language where templates are extremely powerful but their usability is so poor that code like this is kind of idiomatic and even compiles:<p><pre><code> template &lt;class ForwardIt, class T, class Compare&gt;</code></pre> constexpr ForwardIt sb_lower_bound( ForwardIt first, ForwardIt last, const T&amp; value, Compare comp) { auto length = last - first;<p>This looks generic, it compiles by itself, it contains a fancy word “ForwardIt”, and it works if you try it with a vector or an array. But you can’t actually subtract forward iterators, and binary searching a range of forward iterators is quite silly.<p>Rust would have required using the correct trait. Maybe C++ will improve the situation in the long run.
edg-lalmost 2 years ago
Check out <a href="https:&#x2F;&#x2F;en.algorithmica.org&#x2F;hpc&#x2F;data-structures&#x2F;binary-search&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;en.algorithmica.org&#x2F;hpc&#x2F;data-structures&#x2F;binary-searc...</a>
fl0kialmost 2 years ago
Pro tip, the footer link <a href="https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;src&#x2F;core&#x2F;slice&#x2F;mod.rs.html#2520" rel="nofollow noreferrer">https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;src&#x2F;core&#x2F;slice&#x2F;mod.rs.html#2520</a> is not a permalink because it doesn&#x27;t include a commit ID. It looks like it&#x27;s already linking to the wrong thing now (and the article is only two months old), and I don&#x27;t know what the right thing was meant to be. I suggest using GitHub permalinks in future, pinned to specific commits.
postalratalmost 2 years ago
How can you call it branchless if it has &quot;while (length &gt; 0) {&quot;
评论 #37089887 未加载
评论 #37090015 未加载
评论 #37092750 未加载
评论 #37091785 未加载
评论 #37090011 未加载
jezzealmost 2 years ago
Wouldnt it be faster to do length &amp; 1 instead of length % 2 and also length &gt;&gt; 1 instead of length &#x2F; 2? But maybe the compiler does this behind the scenes?
评论 #37088542 未加载
评论 #37088172 未加载
评论 #37088173 未加载
tgvalmost 2 years ago
If the compiler doesn&#x27;t optimize the if(cmp(...)) first += length + rem; perhaps a sign operation could be used when cmp returns a number. Something along the lines of:<p><pre><code> first += (length + rem) * ((sign(cmp(...)) + 1) &#x2F; 2) </code></pre> Has anyone tried that? DDG isn&#x27;t very helpful.
JonChesterfieldalmost 2 years ago
Branches on a comparison function, doesn&#x27;t count it as simple functions end up as a conditional move. Branches in a loop back edge, doesn&#x27;t count it as there&#x27;s a branch predictor.<p>I was hoping for a binary search without branches in it. Not really what we got.
MrYellowPalmost 2 years ago
I am confused.<p>&gt; Same function interface as std::lower_bound, but 2x faster, and shorter. “branchless” because the if compiles down to a conditional move instruction rather than a branch&#x2F;conditional jump.<p>Assembly programmers did &quot;fastest branchless binary searches&quot; using cmov decades ago and we didn&#x27;t need to think about the compiler at all. I know, because I was one of them. Optimizing branches away was and still is a nice pastime. This is not new, or original, and I don&#x27;t understand what&#x27;s noteworthy about it.<p>&quot;Elitist&quot;, you say? Well ... yeah! At least I know what I&#x27;m doing, unlike people who solely rely on and blindly trust the compiler to do their work for them.
sixthDotalmost 2 years ago
<a href="https:&#x2F;&#x2F;probablydance.com&#x2F;2023&#x2F;04&#x2F;27&#x2F;beautiful-branchless-binary-search&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;probablydance.com&#x2F;2023&#x2F;04&#x2F;27&#x2F;beautiful-branchless-bi...</a>
WinLycheealmost 2 years ago
Other fast binary searches <a href="https:&#x2F;&#x2F;github.com&#x2F;sslotin&#x2F;amh-code&#x2F;tree&#x2F;main&#x2F;binsearch">https:&#x2F;&#x2F;github.com&#x2F;sslotin&#x2F;amh-code&#x2F;tree&#x2F;main&#x2F;binsearch</a>
commonlisp94almost 2 years ago
lower_bound and upper_bound are typically implemented in terms of partition_point, which is much more general than this version of lower_bound taking an element.
评论 #37089268 未加载
sesuximoalmost 2 years ago
I’m too lazy to try it, but putting __builtin_unpredictable around the implementation of comp might make std::lower_bound less branchy
1letterunixnamealmost 2 years ago
It&#x27;s not branchless if there are are any Jcc other than JMP because that&#x27;s an opportunity for a branch prediction miss and a pipeline stall.
victor106almost 2 years ago
Anyone knows if Java already does this optimization?
up2isomorphismalmost 2 years ago
Learn assembly: you will get these “tricks” automatically.<p>To be honest it is way more efficient to read these long posts.
notoranditalmost 2 years ago
Branchless?<p>You need at least 1 branch to exit the loop.
评论 #37090345 未加载