Programming Language Benchmark 2

At least some of my understanding could yet survive the morning :grinning:.

Thanks!

I suppose the intent here was to suggest to the compiler that T(1) <= m <= T(20) so the Vector’s normally unfixed size (unliked MVector) has a maximum that can be on the stack? I’m not sure if the compiler can ever tell that from clamp because it could be defined to do something different.

So why not using StaticArrays? There is not only MVector, but also SizedVector

(OT): What I like about this repository is that the code samples do resemble examples of realistic uses of each respective languages.

2 Likes

I find different relative performance, just looking at two languages and two benchmarks

I modified the implementations to run the examples in a loop, like 5 or 10 repetitions. This helps Julia a bit relative to the static languages. But it makes it easier to benchmark. And my benchmark is just as meaningful as the original. (It does not change the relative results much.) I find

  • nqueens. ratio of time Julia / Rust == 1
  • mat_mul ratio of time Julia/Rust == 0.77 (Julia is faster)

I don’t know what the relevant machine characteristics are. But:
Intel(R) Core™ i9-9880H CPU @ 2.30GHz

rustc 1.75.0 (82e1608df 2023-12-21)
julia 1.10.2

7 Likes

This (unneeded branch) can be easily avoided by using an Unsigned type for the bitshift amount (getting us half way there to optimal, 1, not 2, branches and they cost; still by number of instructions, a less important metric not half way there, nor at optimal one instruction), but ironically this has two bugs:

sizeof gives size in bytes (usually), and arguably would have been better in bits to not need 8* because, that works for Int8 up to Int128 (but not BigInt, which is special-cased, since sizeof(BigInt) == 16, though not really… it’s going to stay that way for huge numbers, since this is really a pointer, and more, but not all the data).

I was thinking maybe we need new types in Base: UInt6 for bitshift-count for Int64 (and Int on 64-bit), and UInt5 for Int32 (and Int on 32-bit…), because the % 63 trick I saw in a different thread (for Int) is problematic.

It works on your 64-bit platform, but it’s so easy to forget sometimes Julia code is run on a 32-bit CPU/platform, i.e. on such a platform you shouldn’t hard-code 63 in (your code didn’t, takes that into account and only has the BigInt bug; and potential bug for such new types). You guard away with 8 * sizeof(x) (i.e. working for all built-in types, and this was only pseudo-code after all); and then in alternative fast code with the extra the - 1.

But if we had UInt6 what is sizeof(UInt6)? Is it 1 (stored in one byte), since it must? Or arguably should be 0.75. But we can’t return a float, since we decided on an integer returned (we do not want type-unstable), and your code requires a fraction of 1 to be fully general.

Maybe we need sizeof_in_bits in Base, to think ahead until we get such smaller types, also useful for e.g. nucleotides, and 4-bit (and lower) quantization in neural networks.

I would have thought 2-bit used actually, but we have 4-bit for on-hot encoded… Either way there’s one size in number of bits, and a different sizeof, not consistent, since most likely stored still in 1 byte (in memory, unless you ask for not to), or when in registers (then 8- or likely actually stored in 64-bit one).

julia> bitstring(typemax(Int64) << (63 & (8*sizeof(Int64) - 1)))
"1000000000000000000000000000000000000000000000000000000000000000"

Just note that the bitshift by (0 up to) 63 works there, until it doesn’t from 64 (also for all negative numbers), then the “trick” fails, but you are getting the semantics of the assembly instruction, so i.e. you are promising never to be out of 0…63 bounds. This is why I oppose an operator to make this easy, as in C, to make a mistake, ok with functional-form unsafe_shift (and/or a UInt6 and UInt5 type, then it’s up to you to use the type and correctly).

1 Like