Noalias for dummies

I have not really a clue what the noalias keyword does.

When can we use it to optimize?

2 Likes

If you have a function that takes in two pointers there are certain optimizations you can make if you can assume that the pointers don’t point to the same memory or overlap. I think the most basic example is if you have pointers a and b and you write to a you can still assume that the value in b did not change. For example @memcpy(noalias dest, noalias source) void uses this optimization.

Edit: The official documentation has like no info on this but going off of the similar C keyword restrict I think this means that there is no other alias to this memory

3 Likes

Aha, so it only makes sense with two or more parameters?

If you watched this:

You know about memory regions. Basically noalias makes it so pointers marked by it are guarantied to point in unique memory region. For example here Compiler Explorer even though functions only accepts one parameter second case with noalias is still more optimal since compiler knows pointer can’t point to global and it won’t load global twice.

2 Likes

I think the design of alias in Zig hasn’t been fully finalized yet, which is why the related keyword lacks documentation.

The impact of noalias is reflected in @memcpy and @memmove. The former prohibits overlapping memory copies, allowing for better performance, while the latter allows overlapping memory copies, resulting in worse performance.

Languages like C have rules such as ‘strict aliasing,’ which assume that two pointers of ‘non-compatible types’ do not point to overlapping memory. This is a very controversial feature, and at least Linus does not agree with it.

As far as I know, Zig should also not adhere to strict aliasing rules, but perhaps this means that passing any two pointers, regardless of whether they are of the same type, could potentially be optimized further by specifying noalias.

In addition, when using LLVM’s backend, it still seems to try to make some strict aliasing assumptions, which leads to unexpected results.

1 Like

Now that is very strange for me

What is a “unique memory region”?
Isn’t a ptr parameter just at one (unique) memory location when passed as argument??
I completely don’t get it
 But I will watch that youtube link.

Here, both bar and baz could point to the same location in memory, since the compiler can’t prove that, this isn’t the case it can’t do anything about memory reordering, which in this case doesn’t matter but in a larger function you could loose some optimizations.

pub fn foo(bar : *i32, baz : *i32) i32 {
    bar.* = 1;
    baz.* = 2;
// here the result is actually 2 if baz points to the same location as bar
    return = bar.*; 
}
pub fn foo(noalias bar : *i32, noalias baz : *i32) i32 {
    bar.* = 1;
    baz.* = 2;
// here the result is 1 because baz can't point to the same memory location as bar, it doesn't alias.
    return = bar.*;
}

noalias for dummies

Simply never use it.

6 Likes

That’s one worry less. Protocol:

  • don’t use inline.
  • don’t use noalias

Two related info snippets:

  • I’ve been building all my C/C++ code (including third-party-libs) with -fstrict-aliasing on some platforms since around 2015 and it hasn’t caused any trouble (and apparently at least in GCC this is the default for optimization levels above -O2 anyway)

  • IIRC I’ve encountered exactly one situation where the C equivalent of noalias (e.g. __restrict) made a notable difference in code generation, and where the compiler generated redundant memory store/loads around a callback function call without the __restrict), but this was in a very low level part of my emulator code where the CPU emulator’s tick function was calling into a callback function, since then I have redesigned the emulators to not use callbacks in the first place though.

E.g. at least when it comes to noalias, maybe it’s useful when you’re deep into an profiling/optimization session and staring at compiler output assembly code all day, but not for general use.

4 Likes

indeed, profiling is a must as these things are optimizer pipeline black magic (which changes over time, so reprofiling is necessary)

As an example, I copied XxHash3 and removed all mentions of noalias and inline and ran under ReleaseFast:

Benchmark 1: ./noalias-inline-removed
  Time (mean ± σ):     610.1 ms ±   1.2 ms    [User: 606.8 ms, System: 2.5 ms]
  Range (min 
 max):   609.1 ms 
 612.4 ms    10 runs

Benchmark 2: ./original
  Time (mean ± σ):     609.8 ms ±   1.7 ms    [User: 606.7 ms, System: 2.4 ms]
  Range (min 
 max):   609.1 ms 
 614.6 ms    10 runs

(while keeping in mind that inline functions have a comptime semantic aspect in Zig which sometimes makes it necessary)

2 Likes