diff options
author | Andrew Turner <andrew@FreeBSD.org> | 2016-09-19 13:12:09 +0000 |
---|---|---|
committer | Andrew Turner <andrew@FreeBSD.org> | 2016-09-19 13:12:09 +0000 |
commit | 09a53ad8f1318c5daae6cfb19d97f4f6459f0013 (patch) | |
tree | 2db2658fb2b0e98f2e5771acfa810aead6052d20 /contrib/cortex-strings/scripts | |
parent | bddfc749fafad1d8ccd1f2b612da2a527fe86c1f (diff) | |
parent | 5a194ab47811dee4fd1bd7c1fe163865fb468ae1 (diff) | |
download | src-09a53ad8f1318c5daae6cfb19d97f4f6459f0013.tar.gz src-09a53ad8f1318c5daae6cfb19d97f4f6459f0013.zip |
Notes
Diffstat (limited to 'contrib/cortex-strings/scripts')
-rwxr-xr-x | contrib/cortex-strings/scripts/add-license.sh | 79 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/bench.py | 175 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/fixup.py | 27 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/libplot.py | 78 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/plot-align.py | 67 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/plot-sizes.py | 120 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/plot-top.py | 61 | ||||
-rw-r--r-- | contrib/cortex-strings/scripts/plot.py | 123 | ||||
-rwxr-xr-x | contrib/cortex-strings/scripts/trim.sh | 9 |
9 files changed, 739 insertions, 0 deletions
diff --git a/contrib/cortex-strings/scripts/add-license.sh b/contrib/cortex-strings/scripts/add-license.sh new file mode 100755 index 000000000000..8a6c0710fbbe --- /dev/null +++ b/contrib/cortex-strings/scripts/add-license.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# +# Add the modified BSD license to a file +# + +f=`mktemp -d` +trap "rm -rf $f" EXIT + +year=`date +%Y` +cat > $f/original <<EOF +Copyright (c) $year, Linaro Limited +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Linaro nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +EOF + +# Translate it to C style +echo "/*" > $f/c +sed -r 's/(.*)/ * \1/' $f/original | sed -r 's/ +$//' >> $f/c +echo " */" >> $f/c +echo >> $f/c + +# ...and shell style +sed -r 's/(.*)/# \1/' $f/original | sed -r 's/ +$//' >> $f/shell +echo '#' >> $f/shell +echo >> $f/shell + +for name in $@; do + if grep -q Copyright $name; then + echo $name already has some type of copyright + continue + fi + + case $name in + # These files don't have an explicit license + *autogen.sh*) + continue;; + *reference/newlib/*) + continue;; + *reference/newlib-xscale/*) + continue;; + */dhry/*) + continue;; + + *.c) + src=$f/c + ;; + *.sh|*.am|*.ac) + src=$f/shell + ;; + *) + echo Unrecognied extension on $name + continue + esac + + cat $src $name > $f/next + mv $f/next $name + echo Updated $name +done diff --git a/contrib/cortex-strings/scripts/bench.py b/contrib/cortex-strings/scripts/bench.py new file mode 100644 index 000000000000..476a5322a747 --- /dev/null +++ b/contrib/cortex-strings/scripts/bench.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python + +"""Simple harness that benchmarks different variants of the routines, +caches the results, and emits all of the records at the end. + +Results are generated for different values of: + * Source + * Routine + * Length + * Alignment +""" + +import argparse +import subprocess +import math +import sys + +# Prefix to the executables +build = '../build/try-' + +ALL = 'memchr memcmp memcpy memset strchr strcmp strcpy strlen' + +HAS = { + 'this': 'bounce memchr memcpy memset strchr strcmp strcpy strlen', + 'bionic-a9': 'memcmp memcpy memset strcmp strcpy strlen', + 'bionic-a15': 'memcmp memcpy memset strcmp strcpy strlen', + 'bionic-c': ALL, + 'csl': 'memcpy memset', + 'glibc': 'memcpy memset strchr strlen', + 'glibc-c': ALL, + 'newlib': 'memcpy strcmp strcpy strlen', + 'newlib-c': ALL, + 'newlib-xscale': 'memchr memcpy memset strchr strcmp strcpy strlen', + 'plain': 'memset memcpy strcmp strcpy', +} + +BOUNCE_ALIGNMENTS = ['1'] +SINGLE_BUFFER_ALIGNMENTS = ['1', '2', '4', '8', '16', '32'] +DUAL_BUFFER_ALIGNMENTS = ['1:32', '2:32', '4:32', '8:32', '16:32', '32:32'] + +ALIGNMENTS = { + 'bounce': BOUNCE_ALIGNMENTS, + 'memchr': SINGLE_BUFFER_ALIGNMENTS, + 'memset': SINGLE_BUFFER_ALIGNMENTS, + 'strchr': SINGLE_BUFFER_ALIGNMENTS, + 'strlen': SINGLE_BUFFER_ALIGNMENTS, + 'memcmp': DUAL_BUFFER_ALIGNMENTS, + 'memcpy': DUAL_BUFFER_ALIGNMENTS, + 'strcmp': DUAL_BUFFER_ALIGNMENTS, + 'strcpy': DUAL_BUFFER_ALIGNMENTS, +} + +VARIANTS = sorted(HAS.keys()) +FUNCTIONS = sorted(ALIGNMENTS.keys()) + +NUM_RUNS = 5 + +def run(cache, variant, function, bytes, loops, alignment, run_id, quiet=False): + """Perform a single run, exercising the cache as appropriate.""" + key = ':'.join('%s' % x for x in (variant, function, bytes, loops, alignment, run_id)) + + if key in cache: + got = cache[key] + else: + xbuild = build + cmd = '%(xbuild)s%(variant)s -t %(function)s -c %(bytes)s -l %(loops)s -a %(alignment)s -r %(run_id)s' % locals() + + try: + got = subprocess.check_output(cmd.split()).strip() + except OSError, ex: + assert False, 'Error %s while running %s' % (ex, cmd) + + parts = got.split(':') + took = float(parts[7]) + + cache[key] = got + + if not quiet: + print got + sys.stdout.flush() + + return took + +def run_many(cache, variants, bytes, all_functions): + # We want the data to come out in a useful order. So fix an + # alignment and function, and do all sizes for a variant first + bytes = sorted(bytes) + mid = bytes[int(len(bytes)/1.5)] + + if not all_functions: + # Use the ordering in 'this' as the default + all_functions = HAS['this'].split() + + # Find all other functions + for functions in HAS.values(): + for function in functions.split(): + if function not in all_functions: + all_functions.append(function) + + for function in all_functions: + for alignment in ALIGNMENTS[function]: + for variant in variants: + if function not in HAS[variant].split(): + continue + + # Run a tracer through and see how long it takes and + # adjust the number of loops based on that. Not great + # for memchr() and similar which are O(n), but it will + # do + f = 50000000 + want = 5.0 + + loops = int(f / math.sqrt(max(1, mid))) + took = run(cache, variant, function, mid, loops, alignment, 0, + quiet=True) + # Keep it reasonable for silly routines like bounce + factor = min(20, max(0.05, want/took)) + f = f * factor + + # Round f to a few significant figures + scale = 10**int(math.log10(f) - 1) + f = scale*int(f/scale) + + for b in sorted(bytes): + # Figure out the number of loops to give a roughly consistent run + loops = int(f / math.sqrt(max(1, b))) + for run_id in range(0, NUM_RUNS): + run(cache, variant, function, b, loops, alignment, + run_id) + +def run_top(cache): + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--variants", nargs="+", help="library variant to run (run all if not specified)", default = VARIANTS, choices = VARIANTS) + parser.add_argument("-f", "--functions", nargs="+", help="function to run (run all if not specified)", default = FUNCTIONS, choices = FUNCTIONS) + parser.add_argument("-l", "--limit", type=int, help="upper limit to test to (in bytes)", default = 512*1024) + args = parser.parse_args() + + # Test all powers of 2 + step1 = 2.0 + # Test intermediate powers of 1.4 + step2 = 1.4 + + bytes = [] + + for step in [step1, step2]: + if step: + # Figure out how many steps get us up to the top + steps = int(round(math.log(args.limit) / math.log(step))) + bytes.extend([int(step**x) for x in range(0, steps+1)]) + + run_many(cache, args.variants, bytes, args.functions) + +def main(): + cachename = 'cache.txt' + + cache = {} + + try: + with open(cachename) as f: + for line in f: + line = line.strip() + parts = line.split(':') + cache[':'.join(parts[:7])] = line + except: + pass + + try: + run_top(cache) + finally: + with open(cachename, 'w') as f: + for line in sorted(cache.values()): + print >> f, line + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/fixup.py b/contrib/cortex-strings/scripts/fixup.py new file mode 100644 index 000000000000..003783a49838 --- /dev/null +++ b/contrib/cortex-strings/scripts/fixup.py @@ -0,0 +1,27 @@ +"""Simple script that enables target specific blocks based on the first argument. + +Matches comment blocks like this: + +/* For Foo: abc +def +*/ + +and de-comments them giving: +abc +def +""" +import re +import sys + +def main(): + key = sys.argv[1] + expr = re.compile(r'/\* For %s:\s([^*]+)\*/' % key, re.M) + + for arg in sys.argv[2:]: + with open(arg) as f: + body = f.read() + with open(arg, 'w') as f: + f.write(expr.sub(r'\1', body)) + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/libplot.py b/contrib/cortex-strings/scripts/libplot.py new file mode 100644 index 000000000000..034ffd331a59 --- /dev/null +++ b/contrib/cortex-strings/scripts/libplot.py @@ -0,0 +1,78 @@ +"""Shared routines for the plotters.""" + +import fileinput +import collections + +Record = collections.namedtuple('Record', 'variant function bytes loops src_alignment dst_alignment run_id elapsed rest') + + +def make_colours(): + return iter('m b g r c y k pink orange brown grey'.split()) + +def parse_value(v): + """Turn text into a primitive""" + try: + if '.' in v: + return float(v) + else: + return int(v) + except ValueError: + return v + +def create_column_tuple(record, names): + cols = [getattr(record, name) for name in names] + return tuple(cols) + +def unique(records, name, prefer=''): + """Return the unique values of a column in the records""" + if type(name) == tuple: + values = list(set(create_column_tuple(x, name) for x in records)) + else: + values = list(set(getattr(x, name) for x in records)) + + if not values: + return values + elif type(values[0]) == str: + return sorted(values, key=lambda x: '%-06d|%s' % (-prefer.find(x), x)) + else: + return sorted(values) + +def alignments_equal(alignments): + for alignment in alignments: + if alignment[0] != alignment[1]: + return False + return True + +def parse_row(line): + return Record(*[parse_value(y) for y in line.split(':')]) + +def parse(): + """Parse a record file into named tuples, correcting for loop + overhead along the way. + """ + records = [parse_row(x) for x in fileinput.input()] + + # Pull out any bounce values + costs = {} + + for record in [x for x in records if x.function=='bounce']: + costs[(record.bytes, record.loops)] = record.elapsed + + # Fix up all of the records for cost + out = [] + + for record in records: + if record.function == 'bounce': + continue + + cost = costs.get((record.bytes, record.loops), None) + + if not cost: + out.append(record) + else: + # Unfortunately you can't update a namedtuple... + values = list(record) + values[-2] -= cost + out.append(Record(*values)) + + return out diff --git a/contrib/cortex-strings/scripts/plot-align.py b/contrib/cortex-strings/scripts/plot-align.py new file mode 100644 index 000000000000..524aa20a6c12 --- /dev/null +++ b/contrib/cortex-strings/scripts/plot-align.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +"""Plot the performance of different variants of one routine versus alignment. +""" + +import libplot + +import pylab + + +def plot(records, bytes, function): + records = [x for x in records if x.bytes==bytes and x.function==function] + + variants = libplot.unique(records, 'variant', prefer='this') + alignments = libplot.unique(records, ('src_alignment', 'dst_alignment')) + + X = pylab.arange(len(alignments)) + width = 1.0/(len(variants)+1) + + colours = libplot.make_colours() + + pylab.figure(1).set_size_inches((16, 12)) + pylab.clf() + + for i, variant in enumerate(variants): + heights = [] + + for alignment in alignments: + matches = [x for x in records if x.variant==variant and x.src_alignment==alignment[0] and x.dst_alignment==alignment[1]] + + if matches: + vals = [match.bytes*match.loops/match.elapsed/(1024*1024) for + match in matches] + mean = sum(vals)/len(vals) + heights.append(mean) + else: + heights.append(0) + + pylab.bar(X+i*width, heights, width, color=colours.next(), label=variant) + + + axes = pylab.axes() + if libplot.alignments_equal(alignments): + alignment_labels = ["%s" % x[0] for x in alignments] + else: + alignment_labels = ["%s:%s" % (x[0], x[1]) for x in alignments] + axes.set_xticklabels(alignment_labels) + axes.set_xticks(X + 0.5) + + pylab.title('Performance of different variants of %(function)s for %(bytes)d byte blocks' % locals()) + pylab.xlabel('Alignment') + pylab.ylabel('Rate (MB/s)') + pylab.legend(loc='lower right', ncol=3) + pylab.grid() + pylab.savefig('alignment-%(function)s-%(bytes)d.png' % locals(), dpi=72) + +def main(): + records = libplot.parse() + + for function in libplot.unique(records, 'function'): + for bytes in libplot.unique(records, 'bytes'): + plot(records, bytes, function) + + pylab.show() + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/plot-sizes.py b/contrib/cortex-strings/scripts/plot-sizes.py new file mode 100644 index 000000000000..26a22bc4d6ef --- /dev/null +++ b/contrib/cortex-strings/scripts/plot-sizes.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python + +"""Plot the performance for different block sizes of one function across +variants. +""" + +import libplot + +import pylab +import pdb +import math + +def pretty_kb(v): + if v < 1024: + return '%d' % v + else: + if v % 1024 == 0: + return '%d k' % (v//1024) + else: + return '%.1f k' % (v/1024) + +def plot(records, function, alignment=None, scale=1): + variants = libplot.unique(records, 'variant', prefer='this') + records = [x for x in records if x.function==function] + + if alignment != None: + records = [x for x in records if x.src_alignment==alignment[0] and + x.dst_alignment==alignment[1]] + + alignments = libplot.unique(records, ('src_alignment', 'dst_alignment')) + if len(alignments) != 1: + return False + if libplot.alignments_equal(alignments): + aalignment = alignments[0][0] + else: + aalignment = "%s:%s" % (alignments[0][0], alignments[0][1]) + + bytes = libplot.unique(records, 'bytes')[0] + + colours = libplot.make_colours() + all_x = [] + + pylab.figure(1).set_size_inches((6.4*scale, 4.8*scale)) + pylab.clf() + + if 'str' in function: + # The harness fills out to 16k. Anything past that is an + # early match + top = 16384 + else: + top = 2**31 + + for variant in variants: + matches = [x for x in records if x.variant==variant and x.bytes <= top] + matches.sort(key=lambda x: x.bytes) + + X = sorted(list(set([x.bytes for x in matches]))) + Y = [] + Yerr = [] + for xbytes in X: + vals = [x.bytes*x.loops/x.elapsed/(1024*1024) for x in matches if x.bytes == xbytes] + if len(vals) > 1: + mean = sum(vals)/len(vals) + Y.append(mean) + if len(Yerr) == 0: + Yerr = [[], []] + err1 = max(vals) - mean + assert err1 >= 0 + err2 = min(vals) - mean + assert err2 <= 0 + Yerr[0].append(abs(err2)) + Yerr[1].append(err1) + else: + Y.append(vals[0]) + + all_x.extend(X) + colour = colours.next() + + if X: + pylab.plot(X, Y, c=colour) + if len(Yerr) > 0: + pylab.errorbar(X, Y, yerr=Yerr, c=colour, label=variant, fmt='o') + else: + pylab.scatter(X, Y, c=colour, label=variant, edgecolors='none') + + pylab.legend(loc='upper left', ncol=3, prop={'size': 'small'}) + pylab.grid() + pylab.title('%(function)s of %(aalignment)s byte aligned blocks' % locals()) + pylab.xlabel('Size (B)') + pylab.ylabel('Rate (MB/s)') + + # Figure out how high the range goes + top = max(all_x) + + power = int(round(math.log(max(all_x)) / math.log(2))) + + pylab.semilogx() + + pylab.axes().set_xticks([2**x for x in range(0, power+1)]) + pylab.axes().set_xticklabels([pretty_kb(2**x) for x in range(0, power+1)]) + pylab.xlim(0, top) + pylab.ylim(0, pylab.ylim()[1]) + return True + +def main(): + records = libplot.parse() + + functions = libplot.unique(records, 'function') + alignments = libplot.unique(records, ('src_alignment', 'dst_alignment')) + + for function in functions: + for alignment in alignments: + for scale in [1, 2.5]: + if plot(records, function, alignment, scale): + pylab.savefig('sizes-%s-%02d-%02d-%.1f.png' % (function, alignment[0], alignment[1], scale), dpi=72) + + pylab.show() + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/plot-top.py b/contrib/cortex-strings/scripts/plot-top.py new file mode 100644 index 000000000000..4095239ac815 --- /dev/null +++ b/contrib/cortex-strings/scripts/plot-top.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +"""Plot the performance of different variants of the string routines +for one size. +""" + +import libplot + +import pylab + + +def plot(records, bytes): + records = [x for x in records if x.bytes==bytes] + + variants = libplot.unique(records, 'variant', prefer='this') + functions = libplot.unique(records, 'function') + + X = pylab.arange(len(functions)) + width = 1.0/(len(variants)+1) + + colours = libplot.make_colours() + + pylab.figure(1).set_size_inches((16, 12)) + pylab.clf() + + for i, variant in enumerate(variants): + heights = [] + + for function in functions: + matches = [x for x in records if x.variant==variant and x.function==function and x.src_alignment==8] + + if matches: + vals = [match.bytes*match.loops/match.elapsed/(1024*1024) for + match in matches] + mean = sum(vals)/len(vals) + heights.append(mean) + else: + heights.append(0) + + pylab.bar(X+i*width, heights, width, color=colours.next(), label=variant) + + axes = pylab.axes() + axes.set_xticklabels(functions) + axes.set_xticks(X + 0.5) + + pylab.title('Performance of different variants for %d byte blocks' % bytes) + pylab.ylabel('Rate (MB/s)') + pylab.legend(loc='upper left', ncol=3) + pylab.grid() + pylab.savefig('top-%06d.png' % bytes, dpi=72) + +def main(): + records = libplot.parse() + + for bytes in libplot.unique(records, 'bytes'): + plot(records, bytes) + + pylab.show() + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/plot.py b/contrib/cortex-strings/scripts/plot.py new file mode 100644 index 000000000000..aa2bb1adb560 --- /dev/null +++ b/contrib/cortex-strings/scripts/plot.py @@ -0,0 +1,123 @@ +"""Plot the results for each test. Spits out a set of images into the +current directory. +""" + +import libplot + +import fileinput +import collections +import pprint + +import pylab + +Record = collections.namedtuple('Record', 'variant test size loops src_alignment dst_alignment run_id rawtime comment time bytes rate') + +def unique(rows, name): + """Takes a list of values, pulls out the named field, and returns + a list of the unique values of this field. + """ + return sorted(set(getattr(x, name) for x in rows)) + +def to_float(v): + """Convert a string into a better type. + + >>> to_float('foo') + 'foo' + >>> to_float('1.23') + 1.23 + >>> to_float('45') + 45 + """ + try: + if '.' in v: + return float(v) + else: + return int(v) + except: + return v + +def parse(): + # Split the input up + rows = [x.strip().split(':') for x in fileinput.input()] + # Automatically turn numbers into the base type + rows = [[to_float(y) for y in x] for x in rows] + + # Scan once to calculate the overhead + r = [Record(*(x + [0, 0, 0])) for x in rows] + bounces = pylab.array([(x.loops, x.rawtime) for x in r if x.test == 'bounce']) + fit = pylab.polyfit(bounces[:,0], bounces[:,1], 1) + + records = [] + + for row in rows: + # Make a dummy record so we can use the names + r1 = Record(*(row + [0, 0, 0])) + + bytes = r1.size * r1.loops + # Calculate the bounce time + delta = pylab.polyval(fit, [r1.loops]) + time = r1.rawtime - delta + rate = bytes / time + + records.append(Record(*(row + [time, bytes, rate]))) + + return records + +def plot(records, field, scale, ylabel): + variants = unique(records, 'variant') + tests = unique(records, 'test') + + colours = libplot.make_colours() + + # A little hack. We want the 'all' record to be drawn last so + # that it's obvious on the graph. Assume that no tests come + # before it alphabetically + variants.reverse() + + for test in tests: + for variant in variants: + v = [x for x in records if x.test==test and x.variant==variant] + v.sort(key=lambda x: x.size) + V = pylab.array([(x.size, getattr(x, field)) for x in v]) + + # Ensure our results appear + order = 1 if variant == 'this' else 0 + + try: + # A little hack. We want the 'all' to be obvious on + # the graph + if variant == 'all': + pylab.scatter(V[:,0], V[:,1]/scale, label=variant) + pylab.plot(V[:,0], V[:,1]/scale) + else: + pylab.plot(V[:,0], V[:,1]/scale, label=variant, + zorder=order, c = colours.next()) + + except Exception, ex: + # michaelh1 likes to run this script while the test is + # still running which can lead to bad data + print ex, 'on %s of %s' % (variant, test) + + pylab.legend(loc='lower right', ncol=2, prop={'size': 'small'}) + pylab.xlabel('Block size (B)') + pylab.ylabel(ylabel) + pylab.title('%s %s' % (test, field)) + pylab.grid() + + pylab.savefig('%s-%s.png' % (test, field), dpi=100) + pylab.semilogx(basex=2) + pylab.savefig('%s-%s-semilog.png' % (test, field), dpi=100) + pylab.clf() + +def test(): + import doctest + doctest.testmod() + +def main(): + records = parse() + + plot(records, 'rate', 1024**2, 'Rate (MB/s)') + plot(records, 'time', 1, 'Total time (s)') + +if __name__ == '__main__': + main() diff --git a/contrib/cortex-strings/scripts/trim.sh b/contrib/cortex-strings/scripts/trim.sh new file mode 100755 index 000000000000..dab1047f34f9 --- /dev/null +++ b/contrib/cortex-strings/scripts/trim.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# +# Trims the whitespace from around any given images +# + +for i in $@; do + convert $i -bordercolor white -border 1x1 -trim +repage -alpha off +dither -colors 32 PNG8:next-$i + mv next-$i $i +done |