|
"""Subprocess IPC communication test |
|
|
|
Determine the throughput and bandwidth of standard Python |
|
subprocess interprocess communication. |
|
|
|
""" |
|
|
|
import os |
|
import sys |
|
import time |
|
import argparse |
|
import subprocess |
|
|
|
KB = 1 << 10 |
|
MB = 1 << 20 |
|
|
|
BUFSIZE = 64 * KB |
|
|
|
parser = argparse.ArgumentParser() |
|
parser.add_argument("-o", "--output") |
|
parser.add_argument("-i", "--iterations", default=100, type=int) |
|
parser.add_argument("-b", "--bytes", default=1000, type=int) |
|
parser.add_argument("-s", "--stats", action="store_true", |
|
help="Print additional stats") |
|
parser.add_argument("-p", "--plot", action="store_true") |
|
parser.add_argument("-q", "--quiet", action="store_true") |
|
|
|
opt = parser.parse_args() |
|
|
|
data = "x" * opt.bytes |
|
size = sys.getsizeof(data) |
|
|
|
|
|
if not os.getenv("CHILD"): |
|
popen = subprocess.Popen( |
|
|
|
# Open yourself, but run the below block |
|
[sys.executable, __file__], |
|
|
|
env=dict(os.environ, **{"CHILD": "1"}), |
|
stdin=subprocess.PIPE, |
|
stdout=subprocess.PIPE, |
|
bufsize=-1, |
|
universal_newlines=True, |
|
) |
|
|
|
# Wait for child to boot up.. |
|
response = popen.stdout.readline() |
|
assert response == "Ready\n", "Was: '%s'" % response |
|
|
|
print("Created data {:,} bytes (len {})".format( |
|
size, len(data) |
|
)) |
|
|
|
write_per_iteration = list() |
|
rtrip_per_iteration = list() # roundtrip |
|
child_wait_times = list() |
|
totwrite = 0 |
|
writetime = 0 |
|
|
|
tot0 = time.clock() |
|
for i in range(opt.iterations): |
|
|
|
# Write |
|
write0 = time.clock() |
|
popen.stdin.write(data) |
|
popen.stdin.write("\n") |
|
popen.stdin.flush() |
|
write1 = time.clock() |
|
writetime += write1 - write0 |
|
totwrite += size |
|
|
|
tot1 = time.clock() |
|
totdur = tot1 - tot0 # s |
|
|
|
popen.stdin.close() |
|
popen.stdout.close() |
|
popen.wait() |
|
|
|
def plot(): |
|
import pygal |
|
fname = os.path.join(os.getcwd(), "plot.svg") |
|
print("Plotting to %s.." % fname) |
|
|
|
plot = pygal.Line(width=2000, height=500) |
|
plot.title = "Time per iteration" |
|
plot.add("Roundtrip (ms)", rtrip_per_iteration, show_dots=False) |
|
plot.add("Write to child (ms)", write_per_iteration, show_dots=False) |
|
plot.add("Child wait (ms)", child_wait_times, show_dots=False) |
|
plot.render_to_file(fname) |
|
|
|
if opt.plot: |
|
try: |
|
plot() |
|
except ImportError: |
|
print("Plotting skipped, couldn't find pygal") |
|
|
|
bpi = size |
|
totb = opt.iterations * bpi |
|
bps = totb / (totdur) |
|
mbps = totwrite / MB / writetime |
|
# avgtime = (sum(rtrip_per_iteration) / len(rtrip_per_iteration)) |
|
avgtime = 0 |
|
# mintime = min(rtrip_per_iteration) |
|
mintime = 0 |
|
# maxtime = max(rtrip_per_iteration) |
|
maxtime = 0 |
|
deltime = maxtime - mintime |
|
|
|
results = ( |
|
("iterations", opt.iterations), |
|
("bpi", size * 2), |
|
("bps", bps), |
|
("mbps", mbps), |
|
("totwrite", totwrite / KB), |
|
("totread", avgtime), |
|
("avgtime", avgtime), |
|
("mintime", mintime), |
|
("maxtime", maxtime), |
|
("deltime", deltime), |
|
("totb", totb), |
|
("totdur", totdur), |
|
) |
|
|
|
print("""\ |
|
Write: {mbps:,.3f} MB/s |
|
Iterations: {iterations:,} |
|
Bytes/iteration: {bpi:,} B/i |
|
Bytes/second: {bps:,.0f} B/s |
|
Avarage roundtrip time: {avgtime:.3f} ms |
|
Min roundtrip time: {mintime:.3f} ms |
|
Max roundtrip time: {maxtime:.3f} ms |
|
Delta roundtrip time: {deltime:.3f} ms |
|
Total bytes: {totb:,} B |
|
Total time: {totdur:.3f} s |
|
""".format(**dict(results))) |
|
|
|
if opt.output: |
|
import json |
|
with open(opt.output, "w") as f: |
|
print("Writing results to '%s'" % opt.output) |
|
json.dump(dict(results), f, indent=2, sort_keys=True) |
|
|
|
|
|
else: |
|
totalbytes = 0 |
|
maxread = 32 * KB |
|
|
|
sys.stdout.write("Ready\n") |
|
sys.stdout.flush() |
|
|
|
t0 = time.clock() |
|
|
|
while True: |
|
data = sys.stdin.readline() |
|
|
|
if not data: |
|
break |
|
|
|
totalbytes += sys.getsizeof(data) |
|
|
|
t1 = time.clock() |
|
totaltime = t1 - t0 |
|
|
|
assert totaltime > 0, "Too fast" |
|
rate = float(totalbytes) / MB / totaltime |
|
sys.stderr.write("Read: %0.6f MB/s\n" % rate) |