Paul Khuong

pvk.ca · Personal blog · on HN since 2010

Paul Khuong, a systems programmer, writes about low-level performance, SBCL internals, concurrency, and algorithms like binary search.

What HN saysCommenters call his work worth skimming, cite his posts on memory-ordering fences and binary search, and use his SBCL hacking guide as a reference.
106HN people shared it
48linked it in comments
129stories submitted
2.8Kstory points
4 months agolast shared
2010people per year2026

Most-shared posts

  1. 1
    SBCL: The Ultimate Assembly Code Breadboard
    22 people · 512 points · first shared 2014 · 75 comments on HN
  2. 2
    Binary search is a pathological case for caches
    10 people · 154 points · first shared 2012 · 13 comments on HN
  3. 3
    How to Define New Intrinsics in SBCL
    9 people · 156 points · first shared 2014 · 20 comments on HN
  4. 4
    Binary Search Eliminates Branch Mispredictions (2012)
    8 people · 97 points · first shared 2012 · 18 comments on HN
  5. 5
    Preemption Is GC for Memory Reordering
    7 people · 37 points · first shared 2019 · 1 comments on HN
  6. 6
    Flatter wait-free hazard pointers
    6 people · 10 points · first shared 2020 · discussion on HN
  7. 7
    Starting to Hack on Steel Bank Common Lisp
    5 people · 92 points · first shared 2013 · 20 comments on HN
  8. 8
    Tabasco Sort: a super-optimal merge sort
    4 people · 121 points · first shared 2012 · 5 comments on HN
  9. 9
    All you need is call/cc (2013)
    4 people · 86 points · first shared 2013 · 54 comments on HN
  10. 10
    Baseline implementations should be predictable
    4 people · 23 points · first shared 2021 · 1 comments on HN
  11. 11
    The weaknesses of SBCL's type propagation
    4 people · 3 points · first shared 2013 · discussion on HN
  12. 12
    Homepage
    4 people · first shared 2010
  13. 13
    How to Print Integers Really Fast
    3 people · 176 points · first shared 2017 · 46 comments on HN
  14. 14
  15. 15
    The Unscalable, Deadlock-Prone, Thread Pool
    3 people · 138 points · first shared 2019 · 45 comments on HN

What HN people wrote when they linked it

mahmud · 2010-07-13
Not every programmer needs to be a hardcore systems hacker; you can ignore the low-level details and lead a healthy productive life. But me, personally, I get a massive inferiority complex if I don't dive into Nasm and GDB every 2-4 months. It would pain me not to know useless things about obscure stuff. Yesterday I was prancing through reddit when I followed this link: http://pvk.ca/ My stomach sank when I realized I wouldn't have the time to "follow along" (i.e. waste the day reproducing his results, checking facts, researching and following links) because I am about to start a lead-dev/PM position and need to work on SaaS and EA stuff. So I closed the tab and moved on to focus on what matters. It wasn't until 1AM that I realized what I have done; aarongough sent me a link to his toy prototyping language and I have just spent 5 hours reimplementing it in Lisp, in the process re-digging into the Self papers! (and discovering warts, but that's another story.) I am an Adderall prescription away from leading a healthy productive life, without unnecessary knowledge about stuff.
This is a great article but it goes into a lot of detail that can be intimidating at first. For me, the reading that made asymmetric fences "click" is this: https://pvk.ca/Blog/2019/01/09/preemption-is-gc-for-memory-reordering/ It might be easier to read that first, as it also goes into practical applications, and then this one.
listeria · 2026-05-01
This reminds me of two excellent articles[1][2] by Paul Khuong, in which he talks about using size-specialized binary search for power-of-two sized arrays (special-casing the first iteration for other sizes). He uses conditional moves and defines the number of iterations in advance to ellide the often mispredicted branch, and in the second article goes on to fix cache aliasing issues for large vectors using ternary search. [1]: https://pvk.ca/Blog/2012/07/03/binary-search-star-eliminates-star-bran… [2]: https://pvk.ca/Blog/2012/07/30/binary-search-is-a-pathological-case-fo…
praptak · 2025-07-13
Paul Khuong "Starting to hack on SBCL" has a good list of reference material: https://pvk.ca/Blog/2013/04/13/starting-to-hack-on-sbcl/ Also the rest of his blog is worthy of at least a skim.
metroholografix · 2025-05-07
I see the following in code written by newcomers to CL frequently: (optimize (speed 3) (safety 0)) but it is a very bad habit to get used to, in general, and may create the impression that it's needed for optimized code. Instead, use this where needed: (optimize (speed 3) (safety 1)) which will result in roughly same performance for the vast majority of cases, but keep array bounds checks and (weaker) type checks. For SBCL, there's also the excellent sb-simd [1] plus compiler macros and even hooking into some of the VM internals for custom code generation [2]. [1] https://www.sbcl.org/manual/#sb_002dsimd [2] https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo…
pkhuong · 2024-07-13
I see a lot of people asking for a real use case. If you follow the reference chain in the first aside, you'll find this blog post of mine https://pvk.ca/Blog/2020/07/07/flatter-wait-free-hazard-pointers/#:~:t… where we use value speculation to keep MOVS out of the critical path in an interrupt-atomic read sequence for hazard pointers.
p_l · 2024-05-12
For starters, SBCL's non-CL parts are separated into src/runtime directory, which makes it much easier to figure out what is just "support structure" and what is actual language. For learning more specifically on SBCL, you can look at following https://pvk.ca/Blog/2013/04/13/starting-to-hack-on-sbcl/ https://simonsafar.com/2020/sbcl/ And of course SBCL code and comments. There are also some papers related to CMUCL (and before that, Spice Lisp) that talk about Python Compiler (the compiler used by CMU CL, SBCL, Scieneer CL)
thweoru23432 · 2022-08-05
Actually SBCL (and other CL impls) still follow this tradition; cf. https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo… You can also use the new sb-simd to play around with vectorization in the interpreter. Okay you'll break something, but it's such joy while you do it. C/C++ and other "strictly-separated compilation" languages OTOH seem so dogmatic and stupid now. It's no surprise why development is no painful in the latter. Also not surprising that the world prefers them.
Jach · 2021-11-30
Sounds like it'd be cool for the sake of it, but just in case you (or other readers) aren't aware (Edit -- looks like you are very aware ;) SBCL already compiles Lisp code to native code. It's not the same as (asm) macros all the way down, but still. You can even inspect the assembly of a function with the built-in function DISASSEMBLE, and see how it changes with different optimization levels or type declarations or other things. https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo… is worth a read too for a cool experiment in generating custom assembly for a VM idea.
gautamcgoel · 2020-05-17
Reminds me of this great article on writing a fast mergesort: http://pvk.ca/Blog/2012/08/13/engineering-a-list-merge-sort/
fiddlerwoaroof · 2019-05-14
Sbcl has all the pieces in place for vectorization, but I don’t think anyone has actually hooked them up. However the great thing about lisps is that the compiler and optimizer are written in the language they compile, so an end user can do this sort of thing themselves: https://www.pvk.ca/Blog/2013/06/05/fresh-in-sbcl-1-dot-1-8-sse-intrins…
Veedrac · 2019-01-30
The issue is not just that it's ‘slow’, but that it's frequently slower even than std::map. My suspicion is that the issue is its use of power-of-2 sizes, which is a terrible idea for binary search because it causes cache line aliasing. This is discussed in a lot of detail by Paul Khuong. https://www.pvk.ca/Blog/2012/07/30/binary-search-is-a-pathological-cas… > Even without that hurdle, the slowdown caused by aliasing between cache lines when executing binary searches on vectors of (nearly-)power-of-two sizes is alarming. The ratio of runtimes between the classic binary search and the offset quaternary search is on the order of two to ten, depending on the test case. The approaches there are likely to give significant speedups.
lukego · 2018-06-26
Sounds like a bargain to acquihire pkhuong... https://www.pvk.ca/ ;-)
agentultra · 2014-08-18
TIL: a solid CL implementation can provide safe maps from high-level intrinsic types to low-level implementation types at run-time. Also, SBCL has at least two IRs with each able to provide optimization hints to the code generator and compiler. So cool. I love reading Paul's posts. His bit on Tobasco sort[0] was amazing. [0] http://www.pvk.ca/Blog/2012/08/27/tabasco-sort-super-optimal-merge-sor… update: link
hansvm · 2024-07-25
Yeah, so with capabilities, the naive idea of "categories" might or might not make sense. It depends how much duplication you get (how many pieces of equipment have large counts of capabilities). Most real-world scenarios I've seen only have 2-10 capabilities per piece of equipment though, which makes the simplest, dumbest solution work in the real world (anything fancier pushes closer to the database literature, and there are good solutions, but you hopefully don't have to care). > tens of thousands of tasks Am I misinterpreting, or are you saying that you have tens of thousands of queries and a much lower equipment count? That enables all kinds of solutions. Even on a single machine, multi-thread it. For deletions and insertions, use something like bounded-wait hazard pointers [0] with a bias toward cheap reads. If you have concurrent tasks, horizontal scaling is the name of the game. 10 microseconds is an easy baseline for intra-datacenter networking, easily allowing 10 millisecond reads and mutations even with a distributed solution. Related, simply caching results might get you the performance you want on a single core if you only have hundreds of pieces of equipment you're searching each time. For each capability, just record the last time you mutated it. You can re-use any query result whose associated results don't have potential mutations. Especially if writes are much less frequent than reads, and especially if you have a bias toward not handing out equipment…
tjalfi · 2017-03-27
https://github.com/dym/movitz is a Common Lisp system that runs on bare metal x86. The source code is quite readable. The rest of this post is an excerpt from an email I sent 6 years ago. The following comments on runtime systems are partially based on a long c.l.l thread with posts by Lucid, Symbolics, and Franz alumni. Franz uses a 3-layer approach: CL, a low-level Lisp, and C. Lucid started with Lisp that generated assembler but reluctantly added some C. Symbolics Lisp Machines used bootstrap code in a Pascal-level language with prefix syntax. A Symbolics alum said that in retrospect they should have used C. Most Lisp implementations have subprimitives - low-level functions that can circumvent the type system, often with a prefix such as % or :. Assembly language integration dates to Lisp 1.5 and there are several common approaches. 1. turn the optimizer off - this is easy to use and implement. 2. optimize the assembler block - Naughty Dog GOAL did this. 3. annotate the assembler block with pragmas that indicate side effects. This can be error prone and difficult to use. https://www.pvk.ca/Blog/2014/08/16/how-to-define-new-intrinsics-in-sbc… is an example of this approach. Edited to add the SBCL intrinsic link.
616c · 2016-07-28
I do get that, as an amateur Lisper. I have played with CCL and SBCL on and off for a while. Ironically, the AUR package I used Clozure Lisp with does not even exist anymore on Arch AUR repos. https://aur.archlinux.org/packages/ccl-bin/?comments=all (That won't work) I get the trade off, but if you read SBCL dev blogs, like PVK, you will recognize really competent programmers and elegant solutions. https://pvk.ca/ The problem is these gods (I revere them) cannot possibly encode all the hacks and knowledge. They have to code, and the nuances of such "hacks" are internalized by them and forgotten. Ironically, a lot of shit talk on Lisp transpires here, and it is one of the most sophisticated developer-productive tools I have seen, with many laudable features other toolchains brag about. Lisps had them for years. This is not to be an elitist jackass; the cultural shift to obsolence (sadly, my view) is why only diehards know and others avoid implementation details and functionality: we don't use it, so we don't care. Either way, dead code is scary, especially in such scenarios with binary image dumping and bit fiddling hacks. I was ironically aware of such issues with SBCL when I stumbled on Gentoo Hardened list info when people explained how Lisps have trouble in such constrained environments. https://archives.gentoo.org/gentoo-hardened/message/d2fb14f123e7db18dc… I worry later on such methods leave blobs of weak executable routines and other items of…
guenthert · 2025-07-14
In embedded systems using MCUs with very little memory (AVR comes to mind) assembly might be preferred over high level languages (very generously counting C as HLL here). It might even be fun (for truly launch-and-forget projects -- don't even think about maintaining such code) to forgo structured programming and get creative with control flow and self-modifying code. As a systems engineer it's good to know _some_ x86-64 assembly as sooner or later you're facing a stack trace / register dump and will have to try to make sense of it. Personally, I would take it only further, if you're interested in building compilers. As an odd twist to the whole assembly vs. HLL dichotomy, sbcl is used by some as assembly playground: https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo…
dapperdrake · 2025-02-17
Registers vs x87 stack with free swap a.k.a. rotating stack. Sub-optimal edge cases for both exist: [1] http://cr.yp.to/qhasm/20050210-fxch.txt [2] https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo…
senderista · 2024-03-02
GrumpySloth · 2023-12-17
Measurements suggest that binary search is at least competitive with linear search and is often the winner, so it’s a reasonable default[1]. 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-eliminates-star-… [2]: <https://www.pvk.ca/Blog/2012/07/30/binary-search-is-a-pathological-cas…
cylinder714 · 2023-07-08
SBCL: the ultimate assembly code breadboard (2014): http://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadboa…
mtlmtlmtlmtl · 2023-07-01
https://pvk.ca/Blog/2014/08/16/how-to-define-new-intrinsics-in-sbcl/ Some quick googling led me to this. Can't vouch for it though, since I need to learn this stuff too! Can't find much documentation for DEFTRANSFORM though, unfortunately.
reikonomusha · 2022-08-29
This is entirely inaccurate and a fundamental misunderstanding of Lisp, both as a language and as an ecosystem. Have you seen [0], for example? It's how to leverage the native assembler of a Common Lisp compiler to build a small JIT-like compiler in Common Lisp. Did you know Lisp has a disassembled built-in, with the standard library function DISASSEMBLE [1]? Why would Lisp support these things like "native compilation" are supposedly out of scope? [0] https://pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadbo… [1] http://www.lispworks.com/documentation/lw70/CLHS/Body/f_disass.htm
chc4 · 2022-03-23
For the record: Returning from an interrupt is fully serializing on x86, which I believe means that all modern operating systems on x86 will handle this properly. https://pvk.ca/Blog/2019/01/09/preemption-is-gc-for-memory-reordering/ is a very good blog post about exploiting this for a high-performance membarrier daemon.
eloff · 2020-07-20
Binary search can be implemented using conditional move instructions instead of branching. Given the branches are unpredictable, this yields a huge speedup. It can be implemented like this in pure C, if written in a way the compiler can recognize. In my experience this is the fastest binary search. [1] https://pvk.ca/Blog/2012/07/03/binary-search-star-eliminates-star-bran…
MaulingMonkey · 2020-06-26
Not only is it wasting memory, but I wouldn't be convinced it'd not be outright hurting performance by increasing cache line aliasing, increasing TLB overhead and misses/invalidations! Avoiding page alignment can be a performance gain! https://pvk.ca/Blog/2012/07/30/binary-search-is-a-pathological-case-fo… Like, sure, mapping your executable's code section in at a page boundary is probably fine, but I think trying to align individual functions to page boundaries would be a counterproductive mistake as a general strategy.
saurik · 2020-04-25
Everyone is talking about this microoptimization on the math of the access but all I can think about is how the data structure you are building using this is probably memory bandwidth limited and powers-of-two storage is almost definitely going to cause some kind of cache line aliasing, so maybe you should try something non-obvious like "division by 3" (after doing three key comparisons instead of one) and see if it makes your algorithm much much faster than messing around with a division by 2; there was even some good analysis of this effect a while back I can reference. https://pvk.ca/Blog/2012/07/30/binary-search-is-a-pathological-case-fo…

Keyboard shortcuts

/Search
j kNext / previous blog
EnterOpen selected blog
vVisit the blog site
fFollow / unfollow
lSwitch grid / list view
dSwitch dark mode
g hHome
g pTop posts
g tTopics
g fFollowing
g lLiked
g yHistory
g qFAQ
EscBack / close