Why did disabling hyperthreading make my server slower?

I have a server that's primarily running a Ruby script. Because Ruby (2.7) has a GIL, it is single threaded.

My computer (server) has an Intel i3 dual core processor, but due to hyperthreading I see 4 cores. Ruby only utilizes 25% CPU under heavy load. I wanted to see if disabling hyperthreading benefits a programming language that runs on single thread.

Also, my server is running a very minimal desktop environment and it doesn't use more than 2% CPU. So I wanted to make most of the resources available to Ruby. I did a benchmark to see if I really get any performance boost by disabling hyperthreading.


Benchmark:

I wrote a simple Ruby script that runs a while loop and adds a the value of the loop counter with another variable. This program should use 100% of a CPU core:

#!/usr/bin/env ruby
$-v = true

LOOPS = ENV['N'].to_i.then { |x| x < 1 ? 100_000_000 : x } + 1
i, j, t = 0, 0, Time.now

puts "Counting till #{LOOPS - 1} and adding values to V..."
while (i += 1) < LOOPS
    if i % 10000 == 0
        e = Time.now - t
        r = LOOPS.*(e)./(i).-(e).round(2)
        print "\e[2KN: #{i} | Done: #{i.*(100) / LOOPS}% | Elapsed: #{e.round(2)}s | Estimated Rem: #{r}s\r"
    end

    j += i
end

puts "\nV = #{j}\nTime: #{(Time.now).-(t).round(2)}s"
  • With Hyperthreading:
⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.55s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.55s

⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.54s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.54s

⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.67s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.67s

gnome-system-monitor reported 25% CPU usage by Ruby while the test was running.

  • Without Hyperthreading:

[ # echo 0 | tee /sys/devices/system/cpu/cpu{2,3}/online used to disable hyperthreads ]

⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.72s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.72s

⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.54s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.54s

⮚ ruby p.rb
Counting till 100000000 and adding values to V...
N: 100000000 | Done: 99% | Elapsed: 4.56s | Estimated Rem: 0.0s
V = 5000000050000000
Time: 4.56s

gnome-system-monitor reported 50% CPU usage by Ruby while the test was running.


I have even ran the test on my laptop, which takes around twice the time it took on my computer. But the result is identical: disabling hyperthreading doesn't help the process to do better. And even worse, my laptop gets a bit slower when multitasking.

So in the non-hyperthreading mode, Ruby used 2x the CPU power compared to the hyperthreaded mode. But why did it still take the same amount of time to complete the same task?


Solution 1:

Your Ruby program did not use 2x the CPU time when running with HT disabled. Rather, as it maximizes one core out of two total cores, gnome-system-monitor will report as the utilization as 50%. If, due to HT, the system reports four total cores, one core out of four would be 25%.

Disabling HT did cause more variation in your results because less resources were available: recent Intel (or AMD) cores are quite wide, so additional threads are often useful to extract 10-20% more aggregate performance. If some background process was automatically executed during the test runs, the system without HT is prone to more variance and lower total throughput.

Solution 2:

I wanted to see if disabling hyperthreading benefits a programming language that runs on single thread.

I don't know how cutting the number of cores would improve performance, even for a single threaded app. When hyperthreading is enabled, your cpu is running with 4 virtual cores. A single threaded app using all the cpu it can would use 25% of the available CPU. When you disabled hyperthreading, you took the number of cores down to 2. Now that single threaded app can use 50% of the available CPU.

Ruby isn't using 2x the CPU, it's that you have 1/2 the CPU available when you disable hyperthreading. If you have a large cup 1/4 full of water and pour it into a smaller cup that becomes 1/2 full of water, you still have the same amount of water.

I have even ran the test on my laptop, which takes around twice the time it took on my computer. But the result is identical: disabling hyperthreading doesn't help the process to do better. And even worse, my laptop gets a bit slower when multitasking.

Yes, you are taking away about 1/2 the power of your CPU. That can make the Ruby thread run slower also. Say you have 3 threads that want to be running at the same time in addition to your Ruby thread. If you cut the virtual cores down to 2, it's more likely that your Ruby thread will be paused at least a little to let another thread have sime time.