The sensor-dht11 utility from the WildlifeSystems project has been
rewritten from Python to C to reduce dependencies, improve startup time,
and provide more reliable sensor readings on Raspberry Pi devices. This
report analyses benchmark results comparing the performance of the two
implementations.
Improving the performance of a sensor that is read infrequently (once per minute) may not seem critical, but the current deployment at the Natural History Museum’s Urban Research Station involves reading 25 DHT11 sensors every minute across multiple nodes. Over a year this is over 13 million reads.
The DHT11 sensor requires precise timing for communication, and is known to be tricky to read reliably. Both implementations include retry logic with back-off delays when a read fails, so the benchmark measures both the time taken per read and the number of attempts required.
The source code is available at github.com/Wildlife-Systems/sensor-dht11.
The DHT11 is a low-cost digital temperature and humidity sensor commonly used in environmental monitoring applications. It uses a single-wire serial interface to communicate with the host microcontroller or computer, transmitting 40 bits of data (16-bit humidity, 16-bit temperature, 8-bit checksum) in response to a start signal.
Key characteristics:
The timing-critical nature of the DHT11 protocol makes it challenging to read reliably from user-space on Linux systems, where process scheduling can introduce jitter. Both implementations include retry logic to handle failed reads.
The original implementation used Python 3 with the Adafruit CircuitPython libraries:
adafruit-circuitpython-dht, adafruit-blinka,
RPi.GPIOThe new implementation is a single native C binary:
libgpiod shared
libraryTwo complementary benchmarks were conducted to evaluate performance:
This benchmark measures the internal sensor read time within each implementation, bypassing process startup overhead. It captures the time spent communicating with the DHT11 sensor and processing the response.
This benchmark measures the total end-to-end execution time of invoking each binary, including process startup, library loading, sensor reading, and JSON output generation. This reflects real-world usage where the binary is called by an external scheduler.
/usr/bin/time)This benchmark measures the internal sensor read time within each implementation, including retry delays when a read fails. The benchmark scripts call the sensor reading functions directly, bypassing startup overhead.
Note: 1 C read(s) and 0 Python read(s) failed after exhausting all retry attempts and are excluded from the analysis.
| Implementation | Mean Time (s) | Median Time (s) | SD Time (s) | Min Time (s) | Max Time (s) | Mean Attempts |
|---|---|---|---|---|---|---|
| C | 0.1387 | 0.0258 | 0.3793 | 0.0253 | 9.5838 | 1.6978 |
| Python | 0.3141 | 0.2628 | 0.1457 | 0.2615 | 1.8183 | 1.1205 |
Key finding: The C implementation averages 0.1387 seconds per read compared to 0.3141 seconds for Python — a 2.26× speedup.

The DHT11 sensor requires precise timing for communication. When a read fails, the implementations retry with a backoff schedule. Fewer retry attempts indicates more reliable first-attempt reads.

First-attempt success rates: C implementation: 61.8% | Python implementation: 89.6%

This shows how the total time accumulates over the benchmark run, demonstrating the overall efficiency difference.

Total benchmark time: C implementation completed in 277.23 seconds vs 628.16 seconds for Python — saving 350.93 seconds (55.9%).
To select an appropriate statistical test, we first assess whether the data are normally distributed using the Shapiro-Wilk test:
Both distributions depart significantly from normality (p < 0.05), as expected given the right-skewed distribution caused by retry delays. We therefore use the non-parametric Wilcoxon rank-sum test.
The difference in read times between the C and Python implementations is statistically significant.
This benchmark measures the total end-to-end execution time of running each binary, including process startup, library loading, sensor reading, and JSON output. This reflects real-world usage where the binary is invoked externally (e.g., by a scheduler).
The benchmark script (run_system_benchmarks.sh)
uses /usr/bin/time to measure real, user, and system CPU
time for each invocation.
| Implementation | Mean Real Time (s) | Median Real Time (s) | SD Real Time (s) | Mean User Time (s) | Mean Sys Time (s) | Success Rate |
|---|---|---|---|---|---|---|
| C (local) | 0.414 | 0.20 | 0.6302 | 0.0018 | 0.0319 | 100% |
| Python (system) | 0.500 | 0.47 | 0.2510 | 0.1462 | 0.0827 | 100% |
Key finding: The C binary averages 0.41 seconds per invocation compared to 0.50 seconds for the Python binary — a 1.21× speedup in total execution time.

The system benchmark also captures user and system CPU time, showing where time is spent. “I/O Wait” is the difference between real (wall clock) time and CPU time, representing time spent waiting for the sensor to respond.

The Python implementation spends significantly more time in user-space CPU (loading interpreter and libraries) compared to the minimal overhead of the C binary.
Two complementary benchmarks demonstrate the performance improvements
from migrating sensor-dht11 from Python to C:
Based on 1,999 sensor reads per implementation:
Based on 100 end-to-end binary invocations:
These results validate the migration from Python to C for the
sensor-dht11 utility, particularly for embedded Raspberry
Pi deployments in the WildlifeSystems project where resource efficiency
is critical.
Annual impact: With 25 sensors reading once per minute, the deployment performs 13,140,000 invocations per year. At a saving of 0.09 seconds per invocation, the C implementation saves approximately 13.1 days of wall-clock time and 29.7 days of CPU time annually across the network.