{ ^_^ } sinustrom Solving life, one problem at a time!

Raspberry Pi 3B heatsink benchmark

2019-08-16
Author: Zoltan Puskas
Categories: electronics benchmark
Tags:

The Raspberry Pi single board computer will throttle under heavy load to prevent the CPU core from overheating. Most boards when purchased will come without a heatsink or just with a small one. However there are custom designed, differently sized heatsinks some even with a tiny fan. The tiny heatsinks merely delay full thermal throttling, while medium sized ones reduce the amount of throttling. I was wondering if it’s possible to get full compute power out of my Raspberry Pi 3B only using passive cooling or is it doomed to throttle at full utilization unless I get active cooling.

CPU thermal throttling

Having an unused Raspberry Pi 3B laying around I decided to run BOINC on it, however it quickly became obvious, that with full load on all cores, the mini board computer throttles soon after it starts number crunching.

The board runs on a Broadcom BCM2837 chip, based on the ARM Cortex-A53 architecture, which is basically a mobile/tablet oriented chip. This means that it was designed with power efficiency in mind, where the CPU mostly runs idle, but for short bursts of load still has high performance. Scientific computation running 24/7 on it however will fall outside of this assumption, introducing the thermal throttling problem, and thus reducing computational throughput.

The CPU has 4 cores, each capable running at 1.2GHz at peak, however once the core temperature reaches ~81°C it will start scaling the frequency back until the core stops overheating. In order to keep the performance high the chip needs to be sufficiently cooled.

Benchmarking

Setup

The tested device is a Rapsberry Pi 3B with a Samsung 16GB uSDHC UHS-I Class-10 card, powered by a Cana Kit 2.5A uUSB power supply.

The Raspberry Pi 3B was running Raspbian GNU/Linux 10 (buster) minimal installation with:

  • Linux kernel: 4.19.57-v7+
  • BOINC client: 7.14.2+dfsg-3
  • BOINC workloads:
    • Einstein@Home: einsteinbinary_BRP4_1.47_arm-unknown-linux-gnueabihf__NEON_Beta
    • SETI@home: setiathome_8.06_arm-unknown-linux-gnueabihf

The two heatsinks tested are:

  • the original Cana Kit aluminium mini set
Raspberry Pi 3B with Cana Kit aluminium heatsinks
Raspberry Pi 3B with Kintaro heatsink
Raspberry Pi 3B with  heatsinks reused for RAM and uSD

 

An important difference between the two heatsinks is that while the Cana Kit heatsink uses a thermal adhesive tape (~1.5W/mK) to stick to the CPU, the Kintaro uses a silver thermal compound (~8W/mK) as a transfer medium and the heatsink is fixed to the board via M2.5 screws and spacer standoffs.

For external temperature measurements I’ve used a Fluke 87V multimeter with a 80BK-A Integrated (Type-K thermocouple) temperature probe.

External temperature measurement

Precision for external temperature measurements: ±2.2°C probe + (1% + 1°C) multimeter.

For internal core temperature measurements I’ve used the chip’s internal sensors.

Method

Before each measurement I’ve let the board sit powered off and reach room temperature. The system was configured not to run BOINC client on startup. BOINC client was configured to use all CPU cores at 100% when started. Each measurement had the following steps:

  1. Power on board
  2. SSH into the board as soon as it got network connection
  3. Start the measurement loop
  4. Wait ~10 minutes
  5. Take external measurements
  6. Start BOINC client
  7. Wait at least 20 minutes
  8. Take external measurements

External temperature readings were done at the end of each 10 minute interval on the external surfaces of the CPU heatsink, memory chip and uSD card socket. Probe contact was maintained until temperature readings stabilized.

Logging of internal CPU temperature and frequency was done by running the following small script:

#!/bin/sh
vcgencmd measure_clock arm
vcgencmd measure_temp

The script was run in a loop every second, with the output being redirected to the internal storage into a text file:

$ while true; do ./measure.sh >> measurement.txt; sleep 1; done

Load on the computer was obtained by running uptime command.

Additionally I ran BOINC on the board with both heatsinks for a week each, and averaged the credits received per day for the measurement period using BOINCstats to get a proxy for the long term performance of the board. BOINC is a good benchmark for multiple reasons:

  • the measurement can be done over a long period of time to average out noise,
  • guaranteed continuous, very similar, and heavy workload,
  • the BOINC credit system is well calibrated, and will give consistent scores for approximately same amounts of work done,
  • the workload is CPU and RAM heavy, but very light on IO, which is what we need, since the Raspberry Pi storage is very slow, and loads using a lot of IO (e.g. kernel compile) would skew the results, due to the slowing effect of IO wait.

Results

At the time of measurements the ambient temperature was 25.5°C.

Load averages of the system:

  • idle: 0.03, 0.01, 0.00
  • loaded: 4.71, 4.61, 4.48

Cana Kit heatsink

External temperature measurements (at the end of each interval):

  • CPU heatsink - idle: 42.9°C
  • CPU heatsink - loaded: 74.4°C
  • USB/Network heatsink: 51.6°C
  • uSD slot: 50.9°C
  • RAM chip: 54.3°C
Temperature and frequency with Cana Kit heatsinks

The small heatsink has little cooling effect, it takes around a two minutes before we start throttling. The CPU will start throttling around 79.5°C and by the time it reaches the maximum allowed temperature around 82-84°C the CPU frequency is already down oscillating between 818MHz and 926MHz. The reason for the oscillation is most likely due to the limited frequency scaling step size (which is apparently in the range of in 50-60MHz steps), where running at 926MHz produces too much heat, while running at 818MHz does not produce enough to keep pushing the thermal envelope. The system reached equilibrium around the 5 minute mark after launching the computational load.

Notice that around 1600 seconds there is an uptick in frequency and reduction in temperature. This is due to the fact of me handling the board to take measurements at the bottom. Even a slight movement of air increased heat dissipation, thus improving the cooling effect.

Average BOINC credits produced daily based on a week’s compute: 296

Kintaro heatsink

External temperature measurements (at the end of each interval):

  • CPU heatsink - idle: 35.1°C
  • CPU heatsink - loaded: 52.9°C
  • uSD slot : 48.0°C
  • RAM heatsink: 53.0°C
Temperature and frequency with Kintaro heatsink

Looking at the numbers it’s quite obvious that the bigger custom heatsink, which additionally uses a higher conductivity thermal compound to transfer heat between the SoC and itself, does a better job cooling the system. In fact we can operate the board at maximum frequency with purely passive cooling (though note that my board is not placed in a case). Idle temperature is 7°C lower, while loaded temperature is reduced by 20°C, and this shows both on core temperature measurements as well as externally on the heatsinks.

Looking at the graph we can also see the equilibrium state being reached much slower (~16min), which is expected as it takes longer to heat up the larger mass of the bigger heatsink.

I’ve also taken measurements with the new heatsink after 8h and 24h of continuously running with full load just to make sure that there is no slow convergence to the maximum temperature range. The results are the same, the CPU core temperature stays in the comfortable 61-62°C range.

Additionally applying the Cana Kit heatsinks to the bottom chips has helped to reduce the RAM temperature by 1.3°C and the uSD slot temperature by 2.9°C, which should increase the longevity of said components (especially for storage).

To my surprise the measured temperature values match very closely those being specified by Kintaro (within a 1°C range) for this heatsink and board combination, thus making this measurement verifying their results.

Average BOINC credits produced daily based on a week’s compute: 459

Conclusion

The small heatsinks are not completely useless. For workloads where the board sits idle or at very low utilization most of the time, they will increase the time the CPU can burst for at maximum performance, before throttling begins, however for constant loads they are insufficient.

With the new heatsink on I get an additional 50% operating frequency per core even when loaded fully at all times, which allows for using the board’s full potential. This significant additional performance clearly shows on my produced BOINC credits (a.k.a. daily completed scientific computation), also up 50%, which makes sense since performance for this type of load will scale linearly with frequency.

The fact that the results meet Kintaro’s specifications show the great design and craftsmanship of this Japanese product. Although the bigger heatsink costs $9.88, which is a significant amount compared to the cost of the rest of the system ($44 for the Pi + PSU + mini heatsink kit, $11.81 for the uSD card), it’s a worthy purchase and will make the board run faster and cooler, and all of that passively, without any noise.

Data

Measurements

Data processing script

#!/usr/bin/env python
# (c) Zoltan Puskas <sinustrom.info>
# License: GPLv3

import itertools
import matplotlib.pyplot as plt
import numpy as np
import sys
from matplotlib import ticker
from mpl_toolkits.axisartist.parasite_axes import HostAxes, ParasiteAxes

MAX_SAMPLES = 30*60

if len(sys.argv) < 3:
    print(f"No input provided! Usage: {sys.argv[0]} [MEASUREMENT_FILE] [TITLE]")
	sys.exit(1)

title = sys.argv[2]
freq = []  # Frequency in MHz
temp = []  # Temperature in C

with open(sys.argv[1], 'r') as raw_data:
    for freq_line, temp_line in itertools.zip_longest(*[raw_data]*2):
        freq.append(int(freq_line.split("=")[1])/1000000)
        temp.append(float(temp_line.split("=")[1][:-3]))

freq = freq[:MAX_SAMPLES]
temp = temp[:MAX_SAMPLES]
max_temp = max(temp)
min_temp = min(temp)
time = list(range(0, len(freq)))

fig = plt.figure()
fig.suptitle(title, fontsize=24)
host = HostAxes(fig, [0.15, 0.1, 0.65, 0.8])
par = ParasiteAxes(host, sharex=host)
host.parasites.append(par)
host.axis["right"].set_visible(False)
host.set_xticks(np.arange(0, MAX_SAMPLES, 60))
host.set_xticks(np.arange(0, MAX_SAMPLES, 30), minor=True)
par.axis["right"].set_visible(True)
par.axis["right"].major_ticklabels.set_visible(True)
par.axis["right"].label.set_visible(True)
par.axis["right"].minor_ticklabels.set_visible(True)
par.set_yticks(np.arange(min_temp, max_temp, 10))
par.set_yticks(np.arange(min_temp, max_temp, 2), minor=True)
par.yaxis.set_minor_formatter(ticker.FormatStrFormatter("%.1f"))

fig.add_axes(host)

ax = plt.gca()
ax.grid(which='major', axis='y', linestyle='--', alpha=0.7)
par.grid(which='major', axis='y', linestyle='-', alpha=0.8)
par.grid(which='minor', axis='y', linestyle='-', alpha=0.3)

host.set_xlabel("Time [s]")
host.set_ylabel("Frequency [MHz]")
par.set_ylabel("Temperature [°C]")

p1, = host.plot(time, freq, label="Frequency", linewidth=1.5)
p2, = par.plot(time, temp, label="Temperature", linewidth=1.5)

host.legend(loc='upper left')

plt.show()

Kintaro reference data

Kintaro Test Results [Copyright Kintaro.co, 2017] Kintaro Co. Stress test results [Copyright Kintaro.co, 2017]

Content