Comment by henrikl

Comment by henrikl 2 days ago

15 replies

Seeing a systems language like Zig require runtime polymorphism for something as common as standard IO operations seems off to me -- why force that runtime overhead on everyone when the concrete IO implementation could be known statically in almost all practical cases?

nu11ptr 2 days ago

I/O strikes me as one place where dynamic dispatch overhead would likely be negligible in practice. Obviously it depends on the I/O target and would need to be measured, but they don't call them "I/O bound" (as opposed to "CPU bound") programs for no reason.

throwawaymaths a day ago

> why force that runtime overhead on everyone

pretty sure the intent is for systems that only use one io to have a compiler optimization that elides the cost of double indirection... but also, you're doing IO! so usually something else is the bottleneck, one extra indirection is likely to be peanuts.

do_not_redeem 2 days ago

I think it's just the Zig philosophy to care more about binary size than speed. Allocators have the same tradeoff, ArrayListUnmanaged is not generic over the allocator, so every allocation uses dynamic dispatch. In practice the overhead of allocating or writing a file will dwarf the overhead of an indirect call. Can't argue with those binary sizes.

(And before anyone mentions it, devirtualization is a myth, sorry)

  • kristoff_it 2 days ago

    > (And before anyone mentions it, devirtualization is a myth, sorry)

    In Zig it's going to be a language feature, thanks to its single unit compilation model.

    https://github.com/ziglang/zig/issues/23367

    • do_not_redeem 2 days ago

      Wouldn't this only work if there's only one implementation throughout the entire compliation unit? If you use 2 allocators in your app, your restricted function type has 2 possible callees for each entry, and you're back to the same problem.

      • Zambyte 2 days ago

        > A side effect of proposal #23367, which is needed for determining upper bound stack size, is guaranteed de-virtualization when there is only one Io implementation being used (also in debug builds!).

        > In the less common case when a program instantiates more than one Io implementation, virtual calls done through the Io interface will not be de-virtualized, as that would imply doubling the amount of machine code generated, creating massive code bloat.

        From the article

        • yxhuvud a day ago

          I wonder how massive it actually would be. I'm guessing it really wouldn't be all that massive in practice even if it of course is easy to create massive examples using ways people typically don't write code.

      • thrwyexecbrain a day ago

        Having a limited number of known callees is already better than a virtual function (unrestricted function pointer). A compiler in theory could devirtualize every two-possible-callee callsite into `if (function_pointer == callee1) callee1() else callee2()` which then can be inlined at compile time or branch-predicted at runtime.

        In any case, if you have two different implementations of something then you have to switch between them somewhere -- either at compile-time or link-time or load-time or run-time (or jit-time). The trick is to find an acceptable compromise of performance, (machine)code-bloat and API-simplicity.

      • throwawaymaths a day ago

        > Wouldn't this only work if there's only one implementation throughout the entire compliation unit

        in practice how often are people using more than one io in a program?

  • lerno 20 hours ago

    > care more about binary size than speed

    That does not seem to be true if you look at how string formatting is implemented.

ozgrakkurt a day ago

Runtime polymorphism isn’t something inherently bad.

It is bad if you are introducing branching in a tight loop or preventing compiler from inlining things it would inline otherwise and other similar things maybe?