|
I wanted to write that making defensive copies is something you need to do in mutable situations to preserve safety, (not in immutable situations!), but it looks like enough commenters have hit that point. So lets disabuse your mistrust of immutability in another domain! Here is some typical "go fast and mutable!" nonsense code: int foo(int i, int j) {
while (i < 10) {
j += i;
i++;
}
return j;
}
Let's compile it with https://godbolt.org/, turn on some optimisations and inspect the IR (-O2 -emit-llvm). Copying out the part that corresponds to the while loop: 4:
%5 = sub i32 9, %0, !dbg !20
%6 = add nsw i32 %0, 1, !dbg !20
%7 = mul i32 %5, %6, !dbg !20
%8 = zext i32 %5 to i33, !dbg !20
%9 = sub i32 8, %0, !dbg !20
%10 = zext i32 %9 to i33, !dbg !20
%11 = mul i33 %8, %10, !dbg !20
%12 = lshr i33 %11, 1, !dbg !20
%13 = trunc i33 %12 to i32, !dbg !20
tail call void @llvm.dbg.value(metadata i32 poison, metadata !17, metadata !DIExpression()), !dbg !18
tail call void @llvm.dbg.value(metadata i32 poison, metadata !16, metadata !DIExpression()), !dbg !18
%14 = add i32 %1, %0, !dbg !20
%15 = add i32 %14, %7, !dbg !20
%16 = add i32 %15, %13, !dbg !20
br label %17, !dbg !21
17:
%18 = phi i32 [ %1, %2 ], [ %16, %4 ]
Well, would you look at that! Clang decided (even in this hot loop) never to re-assign any of the left-hand-sides, even though my instructions were just: "mutate j in-place. mutate i in-place." |
Immutability is measurably slower. Full stop.
The fact that you can come up with silly, overly simplistic, non-idiomatic anecdotes showing that sometimes a compiler will prefer calculation doesn’t change that. It is a commonly known fact in low level programming that just because you’re calculating something doesn’t make it slower by default.
When Haskell devs can produce a game engine that doesn’t look like PS2 on a 4090, we can chat again about how immutability is supposedly not slow.