PyVISA
PyVISADocs

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

OperationTypical TimeOptimized TimeKey Technique
Single measurement10-50 ms2-5 msReduce NPLC, disable autozero
1000 measurements10-50 s0.5-2 sBurst mode, binary transfer
Large waveform (1 MB)5-30 s0.5-2 sIncrease chunk_size, binary format
Connection setup1-5 s0.1-0.5 sReuse 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

InterfaceTip
USBSet 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.
GPIBLimited to ~1 MB/s. Binary format matters most here.

Performance Targets

OperationTarget
Simple query (*IDN?)< 10 ms
Single DMM reading< 10 ms
1000-point waveform< 1 s
1 MB waveform< 2 s
Connection open< 500 ms

On this page

Deploy your PyVISA test scripts with TofuPilot

Trusted by Lyft, Cobot, Pacific Defense, and more.

TofuPilot provides dashboards and analytics to track serial device testing at scale.

TofuPilot
Get Started