Optimization Guide
How to speed up PyVISA data transfers and measurements. Binary vs ASCII, chunk_size tuning, connection reuse, and benchmarking.
PyVISA defaults are conservative. A few settings changes can make transfers 10-50x faster.
Performance Quick Reference
| Operation | Typical Time | Optimized Time | Key Technique |
|---|---|---|---|
| Single measurement | 10-50 ms | 2-5 ms | Reduce NPLC, disable autozero |
| 1000 measurements | 10-50 s | 0.5-2 s | Burst mode, binary transfer |
| Large waveform (1 MB) | 5-30 s | 0.5-2 s | Increase chunk_size, binary format |
| Connection setup | 1-5 s | 0.1-0.5 s | Reuse connections |
Binary vs ASCII Transfer
ASCII returns human-readable strings like "1.23,4.56,7.89,...". Binary skips the conversion overhead and transfers raw bytes.
import pyvisa
import numpy as np
import time
rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C065089::INSTR")
# ASCII transfer (slow)
start = time.time()
scope.write("CURVE?")
ascii_data = scope.read()
ascii_time = time.time() - start
print(f"ASCII: {ascii_time:.3f}s for {len(ascii_data)} chars")
# Binary transfer (fast)
scope.write("DATA:ENCDG RIBINARY")
scope.write("HEADER OFF")
start = time.time()
raw = scope.query_binary_values("CURVE?", datatype="h")
binary_time = time.time() - start
print(f"Binary: {binary_time:.3f}s for {len(raw)} points")
print(f"Speedup: {ascii_time / binary_time:.1f}x")
scope.close()
rm.close()Log measurements automatically
TofuPilot records test results from your PyVISA scripts, tracks pass/fail rates, and generates compliance reports. Free to start.
Tune chunk_size
PyVISA's default chunk_size is small. For large transfers, increase it to reduce the number of read calls.
scope.chunk_size = 1024 * 1024 # 1 MB (default is often 20 KB)
scope.timeout = 30000 # 30 s for large transfers
# Configure for speed
scope.write("DATA:ENCDG RIBINARY")
scope.write("DATA:WIDTH 1") # 1 byte/sample if 8-bit resolution is enough
scope.write("HEADER OFF")
scope.write("ACQ:STOPAFTER SEQUENCE")
# Acquire
scope.write("ACQ:STATE ON")
scope.query("*OPC?")
scope.write("CURVE?")
raw = scope.read_raw()
waveform = np.frombuffer(raw[2:], dtype=np.int8)
print(f"{len(waveform)} points, {len(raw) / (1024*1024):.1f} MB")Burst Measurements (DMM)
For high-speed DMM readings, configure the instrument to take many samples in one trigger and return them in bulk.
import pyvisa
import time
rm = pyvisa.ResourceManager()
dmm = rm.open_resource("USB0::0x2A8D::0x0101::MY53220001::INSTR")
count = 1000
# Speed over accuracy
dmm.write("CONF:VOLT:DC")
dmm.write("VOLT:DC:NPLC 0.02") # Minimum integration time
dmm.write("VOLT:DC:ZERO:AUTO OFF") # Skip autozero
dmm.write("VOLT:DC:RANGE:AUTO OFF") # Fixed range
dmm.write(f"SAMP:COUN {count}")
dmm.write("TRIG:SOUR IMM")
start = time.time()
dmm.write("READ?")
response = dmm.read()
elapsed = time.time() - start
values = [float(x) for x in response.split(",")]
rate = len(values) / elapsed
print(f"{len(values)} readings in {elapsed:.2f}s ({rate:.0f} readings/s)")
dmm.close()
rm.close()Connection Reuse
Opening and closing VISA connections is slow (1-5 s). Keep connections open for the duration of your test sequence.
import pyvisa
rm = pyvisa.ResourceManager()
# Open once, use many times
instruments = {
"dmm": rm.open_resource("USB0::0x2A8D::0x0101::MY53220001::INSTR"),
"scope": rm.open_resource("USB0::0x0699::0x0363::C065089::INSTR"),
}
for name, inst in instruments.items():
inst.timeout = 10000
print(f"{name}: {inst.query('*IDN?').strip()}")
# Run all measurements with open connections
for i in range(100):
voltage = float(instruments["dmm"].query("MEAS:VOLT:DC?"))
# ... use scope, etc.
# Close everything at the end
for inst in instruments.values():
inst.close()
rm.close()Benchmarking
Measure your actual transfer rates before and after optimization.
import pyvisa
import time
rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C065089::INSTR")
scope.timeout = 30000
def benchmark(label, setup_fn, transfer_fn, trials=5):
setup_fn()
times = []
for _ in range(trials):
start = time.time()
data = transfer_fn()
times.append(time.time() - start)
avg = sum(times) / len(times)
print(f"{label}: {avg:.3f}s avg ({len(data)} points)")
return avg
# ASCII baseline
def setup_ascii():
scope.write("DATA:ENCDG ASCII")
scope.write("HEADER OFF")
def transfer_ascii():
return scope.query("CURVE?").split(",")
# Binary optimized
def setup_binary():
scope.write("DATA:ENCDG RIBINARY")
scope.write("DATA:WIDTH 2")
scope.write("HEADER OFF")
scope.chunk_size = 1024 * 1024
def transfer_binary():
return scope.query_binary_values("CURVE?", datatype="h")
ascii_time = benchmark("ASCII", setup_ascii, transfer_ascii)
binary_time = benchmark("Binary", setup_binary, transfer_binary)
print(f"Speedup: {ascii_time / binary_time:.1f}x")
scope.close()
rm.close()Interface-Specific Tips
| Interface | Tip |
|---|---|
| USB | Set chunk_size to 1 MB. USB bulk transfers handle large payloads well. |
| Ethernet (VXI-11) | Use TCPIP::ip::inst0::INSTR. Set chunk_size to 2 MB. Avoid WiFi. |
| Ethernet (raw socket) | Use TCPIP::ip::5025::SOCKET with read_termination='\n'. Lower latency than VXI-11. |
| GPIB | Limited to ~1 MB/s. Binary format matters most here. |
Performance Targets
| Operation | Target |
|---|---|
Simple query (*IDN?) | < 10 ms |
| Single DMM reading | < 10 ms |
| 1000-point waveform | < 1 s |
| 1 MB waveform | < 2 s |
| Connection open | < 500 ms |