diff --git a/README.md b/README.md index b344bf5..513c98f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ minimal-lexical =============== -[![Build Status](https://api.travis-ci.org/Alexhuszagh/minimal-lexical.svg?branch=master)](https://travis-ci.org/Alexhuszagh/minimal-lexical) -[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) - This is a minimal version of [rust-lexical](https://github.com/Alexhuszagh/rust-lexical), meant to allow efficient round-trip float parsing. minimal-lexical implements a correct, fast float parser. Due to the small, stable nature of minimal-lexical, it is also well-adapted to private forks. If you do privately fork minimal-lexical, I recommend you contact me via [email](mailto:ahuszagh@gmail.com) or [Twitter](https://twitter.com/KardOnIce), so I can notify you of feature updates, bug fixes, or security vulnerabilities, as well as help you implement custom feature requests. I will not use your information for any other purpose, including, but not limited to disclosing your project or organization's use of minimal-lexical. @@ -12,7 +9,7 @@ minimal-lexical is designed for fast compile times and small binaries sizes, at **Similar Projects** -For a high-level, all-in-one number conversion routines, see [rust-lexical](https://github.com/Alexhuszagh/rust-lexical). For language bindings to lexical, see [lexical-capi](https://github.com/Alexhuszagh/rust-lexical/tree/master/lexical-capi). +For a high-level, all-in-one number conversion routines, see [rust-lexical](https://github.com/Alexhuszagh/rust-lexical). **Table Of Contents** diff --git a/assets/timings_timings_posix.svg b/assets/timings_timings_posix.svg new file mode 100644 index 0000000..3332609 --- /dev/null +++ b/assets/timings_timings_posix.svg @@ -0,0 +1,935 @@ + + + + + + + + 2021-09-04T00:13:21.196328 + image/svg+xml + + + Matplotlib v3.4.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/timings.py b/scripts/timings.py new file mode 100755 index 0000000..4c3613c --- /dev/null +++ b/scripts/timings.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +''' + timings + ======= + + Plot the timings from building minimal-lexical. +''' + +import argparse +import json +import subprocess +import os + +import matplotlib.pyplot as plt +from matplotlib import patches +from matplotlib import textpath + +plt.style.use('ggplot') + +scripts = os.path.dirname(os.path.realpath(__file__)) +home = os.path.dirname(scripts) + +def parse_args(argv=None): + '''Create and parse our command line arguments.''' + + parser = argparse.ArgumentParser(description='Time building minimal-lexical.') + parser.add_argument( + '--features', + help='''optional features to add''', + default='', + ) + parser.add_argument( + '--no-default-features', + help='''disable default features''', + action='store_true', + ) + return parser.parse_args(argv) + +def clean(directory=home): + '''Clean the project''' + + os.chdir(directory) + subprocess.check_call( + ['cargo', '+nightly', 'clean'], + shell=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + +def build(args): + '''Build the project and get the timings output.''' + + command = 'cargo +nightly build -Z timings=json' + if args.no_default_features: + command = f'{command} --no-default-features' + if args.features: + command = f'{command} --features={args.features}' + process = subprocess.Popen( + # Use shell for faster performance. + # Spawning a new process is a **lot** slower, gives misleading info. + command, + shell=True, + stderr=subprocess.DEVNULL, + stdout=subprocess.PIPE, + ) + process.wait() + data = {} + for line in iter(process.stdout.readline, b''): + line = line.decode('utf-8') + crate = json.loads(line) + name = crate['target']['name'] + data[name] = (crate['duration'], crate['rmeta_time']) + + process.stdout.close() + + return data + +def filename(basename, args): + '''Get a resilient name for the benchmark data.''' + + name = basename + if args.no_default_features: + name = f'{name}_nodefault' + if args.features: + name = f'{name}_features={args.features}' + return name + +def plot_timings(timings, output): + '''Plot our timings data.''' + + offset = 0 + text_length = 0 + count = len(timings) + 1 + fig, ax = plt.subplots() + bar_height = count * 0.05 + + def plot_timing(name): + '''Plot the timing of a specific value.''' + + nonlocal count + nonlocal text_length + + if name not in timings: + return + duration, rmeta = timings[name] + local_offset = offset + ax.add_patch(patches.Rectangle( + (offset, count - bar_height / 2), duration, bar_height, + alpha=0.6, + facecolor='lightskyblue', + label=name, + )) + local_offset += rmeta + ax.add_patch(patches.Rectangle( + (local_offset, count - bar_height / 2), duration - rmeta, bar_height, + alpha=0.6, + facecolor='darkorchid', + label=f'{name}_rmeta', + )) + local_offset += duration - rmeta + text = f'minimal-lexical {round(duration, 2)}s' + text_length = max(len(text), text_length) + ax.annotate( + text, + xy=(local_offset + 0.02, count), + xycoords='data', + horizontalalignment='left', + verticalalignment='center', + ) + count -= 1 + + def max_duration(*keys): + '''Get the max duration from a list of keys.''' + + max_time = 0 + for key in keys: + if key not in timings: + continue + max_time = max(timings[key][0], max_time) + return max_time + + # Plot in order of our dependencies. + plot_timing('minimal-lexical') + offset += max_duration('minimal-lexical') + + title = 'Build Timings' + ax.set_title(title) + ax.set_xlabel('Time (s)') + + # Hide the y-axis labels. + ax.set_yticks(list(range(1, len(timings) + 2))) + ax.yaxis.set_tick_params(which='both', length=0) + plt.setp(ax.get_yticklabels(), visible=False) + + # Ensure the canvas includes all the annotations. + # 0.5 is long enough for the largest label. + plt.xlim(0, offset + 0.02 * text_length) + plt.ylim(count + 0.5, len(timings) + 1.5) + + # Save the figure. + fig.savefig(output, format='svg') + fig.clf() + +def plot(args): + '''Build and plot the timings for the root module.''' + + clean() + timings = build(args) + path = f'{home}/assets/timings_{filename("timings", args)}_{os.name}.svg' + plot_timings(timings, path) + +def main(argv=None): + '''Entry point.''' + + args = parse_args(argv) + plot(args) + +if __name__ == '__main__': + main()