Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added benchmark framework and improvements to toTextRange

  • Loading branch information...
commit 6d7484599d3fd7c92c9622df893abb177f45e7a4 1 parent 0812b4f
Andrei Alexandrescu authored

Showing 3 changed files with 381 additions and 36 deletions. Show diff stats Hide diff stats

  1. +7 1 posix.mak
  2. +152 15 std/conv.d
  3. +222 20 std/datetime.d
8 posix.mak
@@ -116,6 +116,12 @@ else
116 116 DFLAGS += -O -release -nofloat
117 117 endif
118 118
  119 +ifeq ($(BENCHMARK),1)
  120 + DUNITTESTFLAGS = -unittest -version=StdRunBenchmarks
  121 +else
  122 + DUNITTESTFLAGS = -unittest
  123 +endif
  124 +
119 125 # Set DOTOBJ and DOTEXE
120 126 ifeq (,$(findstring win,$(OS)))
121 127 DOTOBJ:=.o
@@ -259,7 +265,7 @@ endif
259 265
260 266 $(ROOT)/unittest/%$(DOTEXE) : %.d $(LIB) $(ROOT)/emptymain.d
261 267 @echo Testing $@
262   - @$(DMD) $(DFLAGS) -unittest $(LINKOPTS) $(subst /,$(PATHSEP),"-of$@") \
  268 + @$(DMD) $(DFLAGS) $(DUNITTESTFLAGS) $(LINKOPTS) $(subst /,$(PATHSEP),"-of$@") \
263 269 $(ROOT)/emptymain.d $<
264 270 # make the file very old so it builds and runs again if it fails
265 271 @touch -t 197001230123 $@
167 std/conv.d
@@ -4235,33 +4235,170 @@ void toTextRange(T, W)(T value, W writer)
4235 4235 if (isIntegral!T && isOutputRange!(W, char))
4236 4236 {
4237 4237 Unqual!(Unsigned!T) v = void;
4238   - if (value < 0)
  4238 + static if (T.min < 0)
4239 4239 {
4240   - put(writer, '-');
4241   - v = -value;
  4240 + if (value < 0)
  4241 + {
  4242 + put(writer, '-');
  4243 + v = -value;
  4244 + }
  4245 + else
  4246 + {
  4247 + v = value;
  4248 + }
4242 4249 }
4243 4250 else
4244 4251 {
4245 4252 v = value;
4246 4253 }
4247 4254
4248   - if (v < 10 && v < hexdigits.length)
4249   - {
4250   - put(writer, hexdigits[cast(size_t) v]);
4251   - return;
4252   - }
4253   -
4254 4255 char[v.sizeof * 4] buffer = void;
4255 4256 auto i = buffer.length;
  4257 + static immutable char[100]
  4258 + digit1 = "00000000001111111111222222222233333333334444444444"
  4259 + "55555555556666666666777777777788888888889999999999",
  4260 + digit2 = "01234567890123456789012345678901234567890123456789"
  4261 + "01234567890123456789012345678901234567890123456789";
4256 4262
4257   - do
  4263 + for (;;)
4258 4264 {
4259   - auto c = cast(ubyte) (v % 10);
4260   - v = v / 10;
4261   - i--;
4262   - buffer[i] = cast(char) (c + '0');
4263   - } while (v);
  4265 + if (v < 100)
  4266 + {
  4267 + auto r = cast(size_t) v;
  4268 + buffer[--i] = digit2[r];
  4269 + if (r >= 10) buffer[--i] = digit1[r];
  4270 + break;
  4271 + }
  4272 + auto t = v;
  4273 + v /= 100;
  4274 + auto r = cast(size_t) (t - v * 100);
  4275 + i -= 2;
  4276 + buffer[i] = digit1[r];
  4277 + buffer[i + 1] = digit2[r];
  4278 + }
4264 4279
4265 4280 put(writer, buffer[i .. $]);
4266 4281 }
4267 4282
  4283 +unittest
  4284 +{
  4285 + auto a = appender!(char[])();
  4286 +
  4287 + toTextRange(0, a); assert(a.data == "0"); a.clear();
  4288 + toTextRange(0u, a); assert(a.data == "0"); a.clear();
  4289 + toTextRange(int.min, a); assert(a.data == "-2147483648"); a.clear();
  4290 + toTextRange(int.max, a); assert(a.data == "2147483647"); a.clear();
  4291 + toTextRange(uint.max, a); assert(a.data == "4294967295", a.data); a.clear();
  4292 + toTextRange(long.max, a); assert(a.data == "9223372036854775807", a.data); a.clear();
  4293 + toTextRange(long.min, a); assert(a.data == "-9223372036854775808", a.data); a.clear();
  4294 + toTextRange(7, a); assert(a.data == "7"); a.clear();
  4295 + toTextRange(-7, a); assert(a.data == "-7"); a.clear();
  4296 + toTextRange(347, a); assert(a.data == "347"); a.clear();
  4297 + toTextRange(-457, a); assert(a.data == "-457"); a.clear();
  4298 +}
  4299 +
  4300 +version(StdRunBenchmarks) void benchmark_modp_itoa10(uint n)
  4301 +{
  4302 + static void modp_itoa10(int value, char* str)
  4303 + {
  4304 + char* wstr=str;
  4305 + // Take care of sign
  4306 + uint uvalue = (value < 0) ? -value : value;
  4307 + // Conversion. Number is reversed.
  4308 + do *wstr++ = cast(char)(48 + (uvalue % 10)); while(uvalue /= 10);
  4309 + if (value < 0) *wstr++ = '-';
  4310 + *wstr='\0';
  4311 +
  4312 + static void strreverse(char* begin, char* end)
  4313 + {
  4314 + char aux;
  4315 + while (end > begin)
  4316 + aux = *end, *end-- = *begin, *begin++ = aux;
  4317 + }
  4318 +
  4319 + // Reverse string
  4320 + strreverse(str,wstr-1);
  4321 + }
  4322 +
  4323 + char buf[60];
  4324 + int x = 14657;
  4325 + foreach (i; 0 .. n)
  4326 + {
  4327 + modp_itoa10(x, buf.ptr);
  4328 + }
  4329 +}
  4330 +
  4331 +version(StdRunBenchmarks)
  4332 + void benchmark_toTextRangeBaseline(uint n)
  4333 + {
  4334 + static void toTextRange(T, W)(T value, W writer)
  4335 + if (isIntegral!T && isOutputRange!(W, char))
  4336 + {
  4337 + Unqual!(Unsigned!T) v = void;
  4338 + static if (T.min < 0)
  4339 + {
  4340 + if (value < 0)
  4341 + {
  4342 + put(writer, '-');
  4343 + v = -value;
  4344 + }
  4345 + else
  4346 + {
  4347 + v = value;
  4348 + }
  4349 + }
  4350 + else
  4351 + {
  4352 + v = value;
  4353 + }
  4354 +
  4355 + char[v.sizeof * 4] buffer = void;
  4356 + auto i = buffer.length - 1;
  4357 +
  4358 + for (;;)
  4359 + {
  4360 + if (v < 10)
  4361 + {
  4362 + buffer[i] = cast(char) (v + '0');
  4363 + break;
  4364 + }
  4365 + auto c = cast(ubyte) (v % 10);
  4366 + v = v / 10;
  4367 + buffer[i--] = cast(char) (c + '0');
  4368 + }
  4369 +
  4370 + put(writer, buffer[i .. $]);
  4371 + }
  4372 +
  4373 + auto a = appender!(char[]);
  4374 + int x = 14657;
  4375 + foreach (i; 0 .. n)
  4376 + {
  4377 + a.clear();
  4378 + toTextRange(x, a);
  4379 + }
  4380 + }
  4381 +
  4382 +version(StdRunBenchmarks)
  4383 + void benchmark_toTextRange(uint n)
  4384 + {
  4385 + //Appender!(char[]) a;
  4386 + auto a = appender!(char[]);
  4387 + int x = 14657;
  4388 + foreach (i; 0 .. n)
  4389 + {
  4390 + a.clear();
  4391 + toTextRange(x, a);
  4392 + }
  4393 + }
  4394 +
  4395 +// Every module that wants to get benchmarked needs to include this
  4396 +// code
  4397 +version(StdRunBenchmarks)
  4398 +{
  4399 + import std.datetime;
  4400 + unittest
  4401 + {
  4402 + benchmarkModule!("std.conv")();
  4403 + }
  4404 +}
242 std/datetime.d
@@ -119,6 +119,7 @@ import std.stdio;
119 119 import std.string;
120 120 import std.system;
121 121 import std.traits;
  122 +import std.typecons;
122 123
123 124 version(Windows)
124 125 {
@@ -30868,23 +30869,32 @@ auto r = benchmark!(f0, f1, f2)(10_000_000);
30868 30869 writefln("Milliseconds to call fun[0] n times: %s", r[0].to!("msecs", int));
30869 30870 --------------------
30870 30871 +/
30871   -@safe TickDuration[lengthof!(fun)()] benchmark(fun...)(uint n)
30872   - if(areAllSafe!fun)
30873   -{
30874   - TickDuration[lengthof!(fun)()] result;
30875   - StopWatch sw;
30876   - sw.start();
30877   -
30878   - foreach(i, unused; fun)
30879   - {
30880   - sw.reset();
30881   - foreach(j; 0 .. n)
30882   - fun[i]();
30883   - result[i] = sw.peek();
30884   - }
30885   -
30886   - return result;
30887   -}
  30872 +// @safe TickDuration[lengthof!(fun)()] benchmark(fun...)(uint n)
  30873 +// if(areAllSafe!fun)
  30874 +// {
  30875 +// TickDuration[lengthof!(fun)()] result;
  30876 +// StopWatch sw;
  30877 +// sw.start();
  30878 +
  30879 +// foreach(i, unused; fun)
  30880 +// {
  30881 +// sw.reset();
  30882 +// static if (is(typeof(fun[i](times))))
  30883 +// {
  30884 +// fun[i](times);
  30885 +// }
  30886 +// else
  30887 +// {
  30888 +// foreach(j; 0 .. times)
  30889 +// {
  30890 +// fun[i]();
  30891 +// }
  30892 +// }
  30893 +// result[i] = sw.peek();
  30894 +// }
  30895 +
  30896 +// return result;
  30897 +// }
30888 30898
30889 30899 /++ Ditto +/
30890 30900 TickDuration[lengthof!(fun)()] benchmark(fun...)(uint times)
@@ -30897,9 +30907,17 @@ TickDuration[lengthof!(fun)()] benchmark(fun...)(uint times)
30897 30907 foreach(i, unused; fun)
30898 30908 {
30899 30909 sw.reset();
30900   - foreach(j; 0 .. times)
30901   - fun[i]();
30902   -
  30910 + static if (is(typeof(fun[i](times))))
  30911 + {
  30912 + fun[i](times);
  30913 + }
  30914 + else
  30915 + {
  30916 + foreach(j; 0 .. times)
  30917 + {
  30918 + fun[i]();
  30919 + }
  30920 + }
30903 30921 result[i] = sw.peek();
30904 30922 }
30905 30923
@@ -30928,6 +30946,190 @@ version(testStdDateTime) @safe unittest
30928 30946 auto r = benchmark!(f0, f2)(100);
30929 30947 }
30930 30948
  30949 +/**
  30950 +Benchmarks one or more functions, automatically issuing multiple calls
  30951 +to achieve good accuracy. A baseline timing that accounts for
  30952 +benchmarking overheads is kept along with the results and
  30953 +automatically deducted from all timings.
  30954 +
  30955 +The call $(D benchmark(fun)()) first calls $(D fun) once. If the call
  30956 +completed too fast to gather an accurate timing, $(D fun) is called 10
  30957 +times, then 100 times and so on, until a meaningful timing is
  30958 +collected.
  30959 +
  30960 +The returned value is a tuple containing the number of iterations in
  30961 +the first member and the times in $(D TickDuration) format for each
  30962 +function being benchmarked.
  30963 +
  30964 +A timing indistinguishable from the baseline looping overhead appears
  30965 +with a run time of zero and indicates a function that does too little
  30966 +work to be timed.
  30967 +
  30968 +Example:
  30969 +----
  30970 +import std.conv;
  30971 +int a;
  30972 +void fun() {auto b = to!(string)(a);}
  30973 +auto r = benchmark!(fun)(10_000_000);
  30974 +writefln("Milliseconds to call fun() %s times: %s",
  30975 + r[0], r[1][0].to!("msecs", int));
  30976 +----
  30977 +
  30978 +Since the number of iteration is the same for all functions tested,
  30979 +one call to $(D benchmark) should only group together functions of
  30980 +close performance profile; otherwise, slower functions will take a
  30981 +long time to run catching up with faster functions.
  30982 + */
  30983 +auto benchmark2(fun...)()
  30984 +{
  30985 + // Baseline function. Use asm inside the body to avoid
  30986 + // optimizations.
  30987 + static void baseline() { asm { nop; } }
  30988 + alias .benchmark!(fun, baseline) run;
  30989 +
  30990 + uint n = 1;
  30991 + typeof(run(n)) timings;
  30992 + bigloop:
  30993 + for (; n < 1_000_000_000; n *= 10)
  30994 + {
  30995 + timings = run(n);
  30996 + foreach (t; timings[0 .. $ - 1])
  30997 + {
  30998 + if (t.to!("msecs", int) < 100)
  30999 + {
  31000 + continue bigloop;
  31001 + }
  31002 + }
  31003 + break;
  31004 + }
  31005 +
  31006 + //writeln("iterations: ", n);
  31007 + auto baselineMs = timings[$ - 1].to!("msecs", int);
  31008 + //writeln("baseline: ", baselineMs);
  31009 +
  31010 + foreach (i, unused; fun)
  31011 + {
  31012 + auto ms = timings[i].to!("msecs", int);
  31013 + if (ms >= baselineMs + 10)
  31014 + {
  31015 + timings[i] -= timings[$ - 1];
  31016 + //writeln(fun[i].stringof, ": ", timings[i].to!("msecs", int));
  31017 + }
  31018 + else
  31019 + {
  31020 + timings[i] = timings[i].init;
  31021 + //writeln(fun[i].stringof, ": indistinguishable from baseline");
  31022 + }
  31023 + }
  31024 +
  31025 + return tuple(n, /*cast(TickDuration[timings.length - 1])*/ timings);
  31026 +}
  31027 +
  31028 +/**
  31029 +Benchmarks an entire module given its name. Benchmarking proceeds as
  31030 +follows: all symbols inside the module are enumerated, and those that
  31031 +start with "benchmark_" are considered benchmark functions and are
  31032 +timed using $(D benchmark) defined above.
  31033 +
  31034 +This function prints a table containing the benchmark name (excluding
  31035 +the $(D "benchmark_") prefix), the number of calls issued, the average
  31036 +duration per call, and the speed in calls per second.
  31037 +
  31038 +Example:
  31039 +----
  31040 +// file module_one.d
  31041 +import std.file, std.array;
  31042 +
  31043 +void benchmark_fileWrite()
  31044 +{
  31045 + std.file.write("hello, world!");
  31046 +}
  31047 +
  31048 +void benchmark_stringAppend(uint n)
  31049 +{
  31050 + string a;
  31051 + foreach (i; 0 .. n)
  31052 + {
  31053 + a ~= 'x';
  31054 + }
  31055 +}
  31056 +
  31057 +void benchmark_appender(uint n)
  31058 +{
  31059 + auto s = appender!string();
  31060 + foreach (i; 0 .. n)
  31061 + {
  31062 + put(a, 'x');
  31063 + }
  31064 +}
  31065 +
  31066 +// file module_two.d
  31067 +import module_one;
  31068 +
  31069 +void main()
  31070 +{
  31071 + benchmarkModule!module_one();
  31072 +}
  31073 +----
  31074 +
  31075 +The program above prints a table with the benchmark results. Note that
  31076 +a benchmark function may either take no parameters, indicating that
  31077 + the iteration should be done outside, or accept one $(D uint)
  31078 +parameter, indicating that it does iteration on its own.
  31079 +*/
  31080 +void benchmarkModule(string mod)()
  31081 +{
  31082 + write(
  31083 + "benchmark calls t/call calls/sec\n"
  31084 + "------------------------------------------------------------------------------\n");
  31085 +
  31086 + static struct Local
  31087 + {
  31088 + // Import stuff here so we have access to it
  31089 + mixin("import " ~ mod ~ ";");
  31090 +
  31091 + static void doBenchmark()
  31092 + {
  31093 + struct TestResults
  31094 + {
  31095 + string name;
  31096 + uint iterations;
  31097 + TickDuration time;
  31098 + TickDuration timePerIteration;
  31099 + double itersPerSecond;
  31100 + }
  31101 +
  31102 + TestResults[] results;
  31103 + foreach (entity; mixin("__traits(allMembers, " ~ mod ~ ")"))
  31104 + {
  31105 + static if (entity.length >= 10
  31106 + && entity[0 .. 10] == "benchmark_")
  31107 + {
  31108 + auto r = mixin("benchmark2!(" ~ mod ~ "."
  31109 + ~ entity ~ ")()");
  31110 + ++results.length;
  31111 + with (results[$ - 1])
  31112 + {
  31113 + name = entity[10 .. $];
  31114 + iterations = r[0];
  31115 + time = r[1][0];
  31116 + timePerIteration = time / iterations;
  31117 + itersPerSecond = iterations /
  31118 + time.to!("seconds", double);
  31119 + writefln("%-44s %1.3E %1.3Eμs %1.3E",
  31120 + name, cast(double) iterations,
  31121 + timePerIteration.to!("usecs", double),
  31122 + itersPerSecond);
  31123 + }
  31124 + }
  31125 + }
  31126 + }
  31127 + }
  31128 +
  31129 + Local.doBenchmark();
  31130 + writeln(
  31131 + "------------------------------------------------------------------------------");
  31132 +}
30931 31133
30932 31134 /++
30933 31135 Return value of benchmark with two functions comparing.

0 comments on commit 6d74845

Robert Clipsham

Shouldn't this be using \r\n on windows?

Please sign in to comment.
Something went wrong with that request. Please try again.