Re: Why is this code slow?
On Sunday, 24 March 2024 at 22:16:06 UTC, Kdevel wrote: The term containing the `pow` invocation computes the alternating sequence -1, 1, -1, ..., which can be replaced by e.g. ```d immutable int [2] sign = [-1, 1]; n += sign [i & 1] / (i * 2.0 - 1.0); ``` This saves the expensive call to the pow function. I also used this code: ```d import std.stdio : writefln; import std.datetime.stopwatch; enum ITERATIONS = 1_000_000; enum BENCHMARKS = 20; auto leibniz(bool speed = true)(int iter) { double n = 1.0; static if(speed) const sign = [-1, 1]; for(int i = 2; i < iter; i++) { static if(speed) { const m = i << 1; n += sign [i & 1] / (m - 1.0); } else { n += pow(-1, i - 1) / (i * 2.0 - 1.0); } } return n * 4.0; } auto pow(F, G)(F x, G n) @nogc @trusted pure nothrow { import std.traits : Unsigned, Unqual; real p = 1.0, v = void; Unsigned!(Unqual!G) m = n; if(n < 0) { if(n == -1) return 1 / x; m = cast(typeof(m))(0 - n); v = p / x; } else { switch(n) { case 0: return 1.0; case 1: return x; case 2: return x * x; default: } v = x; } while(true) { if(m & 1) p *= v; m >>= 1; if(!m) break; v *= v; } return p; } void main() { double result; long total_time = 0; for(int i = 0; i < BENCHMARKS; i++) { auto sw = StopWatch(AutoStart.no); sw.start(); result = ITERATIONS.leibniz;//!false; sw.stop(); total_time += sw.peek.total!"nsecs"; } result.writefln!"%0.21f"; writefln("Avg execution time: %f\n", total_time / BENCHMARKS / 1e9); } ``` and results: dmd -run "leibnizTest.d" 3.141594653593692054727 Avg execution time: 0.002005 If I compile with leibniz!false(ITERATIONS) the average execution time increases slightly: Avg execution time: 0.044435 However, if you pay attention, it is not connected to an external library and a power function that works with integers is used. Normally the following function of the library should be called: Unqual!(Largest!(F, G)) pow(F, G)(F x, G y) @nogc @trusted pure nothrow if (isFloatingPoint!(F) && isFloatingPoint!(G)) ... Now, the person asking the question will ask why it is slow even though we use exactly the same codes in C; rightly. You may think that the more watermelon you carry in your arms, the slower you naturally become. I think the important thing is not to drop the watermelons :) SDB@79
Re: Why is this code slow?
On Sunday, 24 March 2024 at 22:16:06 UTC, rkompass wrote: Are there some simple switches / settings to get a smaller binary? 1) If possible you can use "betterC" - to disable runtime 2) otherwise ```bash --release --O3 --flto=full -fvisibility=hidden -defaultlib=phobos2-ldc-lto,druntime-ldc-lto -L=-dead_strip -L=-x -L=-S -L=-lz ```
Re: Why is this code slow?
The term containing the `pow` invocation computes the alternating sequence -1, 1, -1, ..., which can be replaced by e.g. ``` immutable int [2] sign = [-1, 1]; n += sign [i & 1] / (i * 2.0 - 1.0); ``` This saves the expensive call to the pow function. I used the loop: ```d for (int i = 1; i < iter; i++) n += ((i%2) ? -1.0 : 1.0) / (i * 2.0 + 1.0); ``` in both C and D, with gcc and gdc and got average execution times: --- C - original: loop replacement: -O2: 0.009989 0.003198 ... 0.001335 --- D - original: loop replacement: -O2: 0.230346 0.003083 ... 0.001309 almost no difference. But the D binary is much larger on my Linux: 4600920 bytes instead of 15504 bytes for the C version. Are there some simple switches / settings to get a smaller binary?
Re: Reworking the control flow for my tactical role-playing game
On Saturday, 23 March 2024 at 04:32:29 UTC, harakim wrote: * You should probably not do this, but it might give you some ideas for later. What I would do is make a separate thread for managing the UI state and push events to that thread through the mailbox. I have done this once (on my third version of a program) and it was by far the cleanest and something I was proud of. The benefit is you can publish those same events to a log and if something in your UI goes wrong, you can look at the log. I was thinking about how I can have one thread doing the rendering and another doing everything else, given that the "everything else" thread would be idle most of the time. I thought about giving the "everything else" thread a queue; an array of functions that it's tasked with going over. Every UI interaction would add one to the queue. It just occurred to me that this must be what you were suggesting here.
Re: Why is this code slow?
On Sunday, 24 March 2024 at 19:31:19 UTC, Csaba wrote: I know that benchmarks are always controversial and depend on a lot of factors. So far, I read that D performs very well in benchmarks, as well, if not better, as C. I wrote a little program that approximates PI using the Leibniz formula. I implemented the same thing in C, D and Python, all of them execute 1,000,000 iterations 20 times and display the average time elapsed. Here are the results: C: 0.04s Python: 0.33s D: 0.73s What the hell? D slower than Python? This cannot be real. I am sure I am making a mistake here. I'm sharing all 3 programs here: C: https://pastebin.com/s7e2HFyL D: https://pastebin.com/fuURdupc Python: https://pastebin.com/zcXAkSEf Usually you do not translate mathematical expressions directly into code: ``` n += pow(-1.0, i - 1.0) / (i * 2.0 - 1.0); ``` The term containing the `pow` invocation computes the alternating sequence -1, 1, -1, ..., which can be replaced by e.g. ``` immutable int [2] sign = [-1, 1]; n += sign [i & 1] / (i * 2.0 - 1.0); ``` This saves the expensive call to the pow function.
Re: Why is this code slow?
On Sunday, 24 March 2024 at 19:31:19 UTC, Csaba wrote: As you can see the function that does the job is exactly the same in C and D. Not really.. The speed of Leibniz algo is mostly the same. You can check the code in this benchmark for example: https://github.com/niklas-heer/speed-comparison What you could fix in your code: * you can use enum for BENCHMARKS and ITERATIONS * use pow from core.stdc.math * use sw.reset() in a loop So the main part could look like this: ```d auto sw = StopWatch(AutoStart.no); sw.start(); foreach (i; 0..BENCHMARKS) { result += leibniz(ITERATIONS); total_time += sw.peek.total!"nsecs"; sw.reset(); } sw.stop(); ```
Re: Why is this code slow?
On Sunday, 24 March 2024 at 19:31:19 UTC, Csaba wrote: ... Here are the results: C: 0.04s Python: 0.33s D: 0.73s ... I think a few things can be going on, but one way to go is trying using optimization flags like "-O2", and run again. But anyway, looking through Assembly generated: C: https://godbolt.org/z/45Kn1W93b D: https://godbolt.org/z/Ghr3fqaTW The Leibniz's function is very close each other, except for one thing, the "pow" function on D side. It's a template, maybe you should start from there, in fact I'd try the pow from C to see what happens. Matheus.
Why is this code slow?
I know that benchmarks are always controversial and depend on a lot of factors. So far, I read that D performs very well in benchmarks, as well, if not better, as C. I wrote a little program that approximates PI using the Leibniz formula. I implemented the same thing in C, D and Python, all of them execute 1,000,000 iterations 20 times and display the average time elapsed. Here are the results: C: 0.04s Python: 0.33s D: 0.73s What the hell? D slower than Python? This cannot be real. I am sure I am making a mistake here. I'm sharing all 3 programs here: C: https://pastebin.com/s7e2HFyL D: https://pastebin.com/fuURdupc Python: https://pastebin.com/zcXAkSEf As you can see the function that does the job is exactly the same in C and D. Here are the compile/run commands used: C: `gcc leibniz.c -lm -oleibc` D: `gdc leibniz.d -frelease -oleibd` Python: `python3 leibniz.py` PS. my CPU is AMD A8-5500B and my OS is Ubuntu Linux, if that matters.
Re: Mutability issue
On Saturday, 23 March 2024 at 20:49:14 UTC, Nick Treleaven wrote: On Saturday, 23 March 2024 at 19:30:29 UTC, Menjanahary R. R. wrote: for (T candidate = T(5); candidate * candidate <= n; candidate += T(6)) { When T is `const int`, the above code declares and initializes a constant variable: ```d const int candidate = const int(5); ``` Then, at the end of each loop iteration, it does: ```d candidate += const int(6); ``` So you are trying to modify a constant. Constants can only be initialized, never assigned. T candidate = (n % T(2) == T(0)) ? n + T(1) : n + T(2); // Start from next Odd for (;; candidate += T(2)) { // Skip even Same here, you declare a constant then try to assign to it at the end of each loop iteration. Thanks for your prompt answer.
Re: Mutability issue
On Saturday, 23 March 2024 at 20:38:40 UTC, Jonathan M Davis wrote: On Saturday, March 23, 2024 1:30:29 PM MDT Menjanahary R. R. via Digitalmars- d-learn wrote: [...] Well, when nextPrime is instantiated, the type of T is inferred from the function argument. So, if num is int, then T is int, whereas if num is const int, then T is const int. The same with isPrime. [...] Thanks for your prompt answer. It works like a charm. It's always a pleasure to learn from the Dlang community.
Re: Mutate immutable inside shared static constructor
On Saturday, 23 March 2024 at 21:59:57 UTC, Nick Treleaven wrote: On Saturday, 23 March 2024 at 21:53:43 UTC, Jonathan M Davis wrote: Yes, it's a bug. It's a clear violation of the type system if a non-mutable variable is ever given a value more than once. It should be initialized, and then it should be treated as illegal to ever assign to it - or to do anything else which would mutate it. So, clearly, the logic in static constructors with regards to non-mutable variables is overly simple at the moment. Thanks, filed: https://issues.dlang.org/show_bug.cgi?id=24449 Thanks for your prompt answer.
Re: impure
On Sunday, March 24, 2024 1:41:41 AM MDT Dom DiSc via Digitalmars-d-learn wrote: > I'm creating a library that is completely pure, but it doesn't > compile with pure: at the top because of one impure unittest > (which uses random to test some things only probabilistic)! > > So do I really need to declare every function pure individually > because of a test?!? > > Can we please have a @impure attribute? > And by the way also @throws and @gc? > That would make live so much easier... It's been brought up a number of times before that it would be desirable to have a way to negate attributes, and maybe we'll get that ability at some point, but for now, we don't have it. The only attributes that can be negated are @safe, @trusted, and @system, because using one of them directly on a function overrides any others that are applied more globally. So, for now, you cannot apply pure to an entire module and then have it not apply to something within the module (though you could put that one test at the top before you apply pure). Another thing you could do would be to use debug {} to ignore attributes within that block (though then that code will only be run when building with -debug). How much sense that makes depends on what your test is doing, but it is a way to get around pure in code that isn't intended to be used in production. All of that being said, I'd be inclined to argue that in general, mass-applying attributes is asking for trouble. It works to a point, but it makes it easy to forget which attributes apply, and in some cases, attributes get ignored when they're mass-applied (though that's mostly on types IIRC). It makes more sense when you're applying an attribute to the entire module and not just a section of a module, but it does have a tendency to become a maintenance problem - particularly when it's code that more than one person works on. It also makes code harder to review, because diffs won't include any of the attributes that are being mass-applied, making it easy to miss the fact that a particular attribute applies to the code being changed. So, yes, you've run into a problem that it would be nice to have a better fix for, but even if we could negate attributes in general, there are good reasons to prefer to avoid mass-applying attributes. - Jonathan M Davis