# Relative performance discrepancy across cpus even with --cpu-target set

**URL:** <https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503>\
**Category:** Performance\
**Tags:** simd, benchmark\
**Created:** [August 13, 2019, 5:09pm UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503 "2019-08-13T17:09:56Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![non-Jedi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/non-jedi/32/3645_2.png) [@non-Jedi](https://discourse.julialang.org/u/non-Jedi)\
**Post date:** [August 13, 2019, 5:09pm UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/1 "2019-08-13T17:09:56Z")

</div>

I’ve been trying to optimize [the benchmarks game’s julia programs](https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/julia.html) again, and I’m running into a problem I don’t really know how to work around.

> **[Julia n-body contribution (#177) · Issues · The Computer Language Benchmarks...](https://salsa.debian.org/benchmarksgame-team/benchmarksgame/-/issues/177)**
>
> CONTRIBUTE SOURCE CODE Should be about a 20% speedup over existing Julia code mostly by taking advantage of automatic SIMD vectorization....

The gist is that between two implementations, one is significantly faster on the benchmarks game [cpu from 2007](https://ark.intel.com/content/www/us/en/ark/products/29765/intel-core-2-quad-processor-q6600-8m-cache-2-40-ghz-1066-mhz-fsb.html), and one is significantly faster on [my more modern laptop processor](https://ark.intel.com/content/www/us/en/ark/products/85213/intel-core-i5-5300u-processor-3m-cache-up-to-2-90-ghz.html). This discrepancy persists even when I set `--cpu-target=core2`. So that AVX instructions aren’t used.

Any thoughts?

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [August 14, 2019, 3:17am UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/2 "2019-08-14T03:17:25Z")

</div>

Enough time elapsed that many small aspects of the design, organization and optimization of cache levels , speculative execution, memory chemistry, threads and active cpu subblock interaction changed. Even small changes inside our computers combine biggly.

You are likely better off going with the implementation that runs fastest on your current laptop. You could also post both and ask if others would take a look.

---

<div class="post-metadata">

**Author:** ![freemint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/freemint/32/20013_2.png) [@freemint](https://discourse.julialang.org/u/freemint)\
**Post date:** [August 14, 2019, 9:01am UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/3 "2019-08-14T09:01:47Z")

</div>

My two cents:

- Old CPU has way more cache
- If your code uses multiple cores that can actually hurt your performance on the newer one, since it is an dual core with two threads per core. If two threads need the same hardware resources they are blocking each other some times

Also interesting is this comparison [UserBenchmark: Intel Core i5-5300U vs Core2 Quad Q6600](https://cpu.userbenchmark.com/Compare/Intel-Core2-Quad-Q6600-vs-Intel-Core-i5-5300U/1980vsm16790) where the younger CPU absolutely dominates, expect for Integer performance where it is the other way around (see under nice to have).

---

<div class="post-metadata">

**Author:** ![non-Jedi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/non-jedi/32/3645_2.png) [@non-Jedi](https://discourse.julialang.org/u/non-Jedi)\
**Post date:** [August 14, 2019, 12:07pm UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/4 "2019-08-14T12:07:22Z")

</div>

> [@JeffreySarnoff](#):
>
> You are likely better off going with the implementation that runs fastest on your current laptop. You could also post both and ask if others would take a look.

So I guess the real question I was trying to get at was: how do I optimize a Julia program for a processor I don’t have access to that has very different performance characteristics than ones I do? I’m not sure there’s actually any sort of good answer to that. Both implementations are in the linked thread in the OP, but they are far too much code to post inline on the forums.

> [@freemint](#):
>
> If your code uses multiple cores that can actually hurt your performance on the newer one, since it is an dual core with two threads per core. If two threads need the same hardware resources they are blocking each other some times

The benchmark in question is pure single-core, double-precision floating point arithmetic. The only parallelism that is feasible with the benchmark is simd.

---

<div class="post-metadata">

**Author:** ![non-Jedi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/non-jedi/32/3645_2.png) [@non-Jedi](https://discourse.julialang.org/u/non-Jedi)\
**Post date:** [August 14, 2019, 12:40pm UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/5 "2019-08-14T12:40:42Z")

</div>

If anyone is curious, here are the portions of each script where the majority of the time is spent. Each of these functions is run on a vector of 5 `Body`s in a loop something like:

```julia
@inbounds for i in 1:length(bodies), j in i+1:length(bodies)
    advance_velocity!(bodies[i], bodies[j], 0.01)
end

```

### Faster on old cpu

This was slightly modified to extract the contents of an inner loop into a discrete function, but doing so causes no performance difference on the new cpu.

```julia
mutable struct Body
    x::Float64
    y::Float64
    z::Float64
    vx::Float64
    vy::Float64
    vz::Float64
    mass::Float64
end

function advance_velocity!(bi::Body, bj::Body, dt::Float64)
    dx = bi.x - bj.x
    dy = bi.y - bj.y
    dz = bi.z - bj.z
    dsq = dx^2 + dy^2 + dz^2
    distance = sqrt(dsq)
    mag = dt / (dsq * distance)

    bi.vx -= dx * bj.mass * mag
    bi.vy -= dy * bj.mass * mag
    bi.vz -= dz * bj.mass * mag

    bj.vx += dx * bi.mass * mag
    bj.vy += dy * bi.mass * mag
    bj.vz += dz * bi.mass * mag
end

```

### Faster on new cpu

**WARNING** : this code commits type-piracy on tuples and should not be run in a REPL session you would like to preserve.

```julia
# 4 floats in tuple instead of 3 generates better SIMD instructions
const V3d = NTuple{4,Float64}
V3d(x=0.0, y=0.0, z=0.0) = (Float64(x), Float64(y), Float64(z), 0.0)

Base.sum(v::V3d) = @inbounds +(v[1], v[2], v[3])

struct Body
    pos::V3d
    v::V3d
    m::Float64
end

Base.@propagate_inbounds function update_velocity(b1, b2, Δt)
    Δpos = b1.pos .- b2.pos
    d² = sum(Δpos .* Δpos)
    mag = Δt / (d² * √d²)

    (Body(b1.pos, muladd.(-b2.m * mag, Δpos, b1.v), b1.m),
     Body(b2.pos, muladd.(b1.m * mag, Δpos, b2.v), b2.m))
end

```

And for the record, on the new cpu, modifying things between using a mutable `Body` and an immutable `Body` doesn’t make any appreciable difference in timing.

---

<div class="post-metadata">

**Author:** ![pixel27](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pixel27/32/8902_2.png) [@pixel27](https://discourse.julialang.org/u/pixel27)\
**Post date:** [August 14, 2019, 8:02pm UTC](https://discourse.julialang.org/t/relative-performance-discrepancy-across-cpus-even-with-cpu-target-set/27503/6 "2019-08-14T20:02:11Z")

</div>

> [@non-Jedi](#):
>
> how do I optimize a Julia program for a processor I don’t have access to that has very different performance characteristics than ones I do?

I think this is pretty much impossible at least at the level you are trying for. Meaning it looks like you are trying to optimize a single operation to run as fast as possible. If you where optimizing a large application then it profile the whole thing, find the bottle necks, improve them, rinse and repeat…and the end if it runs faster on “your” cpu then it should run at least better on other CPUs.

But trying to optimize a tight repeated calculations, that’s going to depend on (as others have said) CPU cache and internal CPU architecture. The best you could probably do look at the generated machine code and ensure that it’s using the assembly you expect and has minimal branching.
