D slower ~1s from C ?!
Marco Leise
Marco.Leise at gmx.de
Sun Apr 8 19:38:10 PDT 2012
Am Thu, 05 Apr 2012 19:22:37 +0200
schrieb "Minas" <minas_mina1990 at hotmail.co.uk>:
> Many of you should know the website "projecteuler.net", where
> there are mathematical problems to solve with computers.
>
> I am doing those in D, and after I finished one today, I decided
> to compile it in C as well to compare the results.
The first rule or PE: You don't talk about PE in the public. You accidentally posted a solution to a problem in the public. Now people can solve the task, using a search engine. And after solving the problem, get access to other language implementations. Some employers even use problems from there to assess new programmers. And believe me, you wouldn't want a co-worker that cheated on PE to get the job ;).
Anyway like others suggested, I use GDC to get the most out of D and I actually like the challenge of staying under 50ms for every problem I solve. (My Friend Key is 58749952297599_fa95cc8e61e8df1206f1144436ac050f)
Admittedly my first solutions aren't always fast. Often I just solve the problem and then look into the forum for how to make it _fast_. After a while I think, I should have a nice library of very fast helper functions.
My personal favorite is a small struct that can be used to iterate along rows or diagonals of Pascal's triangle:
---------------------
alias size_t ℕ;
/**
* The ultimate tool to move around in Pascal's triangle. Pascal(0, 0) is the
* top of the triangle with the value of 1.
*/
struct Pascal
{
this(ℕ n, ℕ k) {
// internally we offset these by +1, to simplify the math
++n; ++k;
_n = n;
if (k > n / 2) {
_k = _n;
left(_n - k);
} else {
right(k - 1);
}
}
ℕ left(ℕ amount) {
while (amount--) {
_value *= --_k;
_value /= _n - _k;
}
return _value;
}
ℕ right(ℕ amount) {
while (amount--) {
_value *= _n - _k;
_value /= _k++;
}
return _value;
}
ℕ rightDown(ℕ amount) {
while (amount--) {
_value *= _n++;
_value /= _k++;
}
return _value;
}
ℕ leftDown(ℕ amount) {
while (amount--) {
_value *= _n++;
_value /= _n - _k;
}
return _value;
}
ℕ leftUp(ℕ amount) {
while (amount--) {
_value *= --_k;
_value /= --_n;
}
return _value;
}
ℕ rightUp(ℕ amount) {
while (amount--) {
_value *= _n - _k;
_value /= --_n;
}
return _value;
}
@property
ℕ value() { return _value; }
alias value this;
private:
ℕ _n = 1, _k = 1;
ℕ _value = 1;
}
-------------------------
I use it like this for combinatorics calculations (n over k), where a naive approach would recalculate that Pascal's triangle value at the required position over and over again:
ℕ[] permut_per_variation = new ℕ[variations];
Pascal pascal = Pascal(_2 + _3, _3);
foreach (v; 0..variations)
{
permut += permut_per_variation[v] = pascal;
pascal.leftDown(1);
pascal.left(2);
}
Still some functional programmers keep mocking me for my lines of code count. And I tend to agree now and then (some FP solutions are _really_ short). With these fun tasks it is more a matter of personality though. I prefer fast over concise code. Some participants even use ASM... maybe they like it really close to the metal. In any case it is good to know how C/D code maps to machine code, e.g. what is efficient and what not, so you don't misuse real instead of int.
Looking at your code I noticed the following:
*) writeln() is ok, but doesn't flush (e.g. missing output when a program is halted in a debugger or crashes)
You can use stdout.writeln() when you need the output on the screen as soon as the line is executed
*) n = abs(n); // <- don't need this, you already declared n as unsigned
*) if( n % 2 ...)
Just in case someone wonders, GCC/GDC replaces the modulo with faster
code. There is no need to write n & 1, which is a bit more cryptic.
*) for(ulong i = 2; i <= cast (ulong)sqrt(cast(double)n+1); ++i)
can be written as:
foreach (i; 2 .. cast(ulong)sqrt(n)+1)
Foreach only computes start and end once and 'i' will be ulong thanks to the cast!
*) ulong power = cast(ulong)pow(10, cast(ulong)log10(n));
can alternatively be written as:
ulong power = 10 ^^ cast(ulong)log10(n);
*) In the following while loop, you can replace
power = cast(ulong)pow(10, cast(ulong)log10(n));
with
power /= 10;
But really, only the isPrime() function is taking up time in this code. This is a short and fast version with GDC 64bit, but a different algorithm would help more:
bool isPrime(ulong n)
{
// 0 and 1 aren't primes
if( n < 2 ) return false;
for( ulong i = 2; n / i >= i; ++i )
if( n % i == 0 )
return false;
return true;
}
When / and % are used close to each other, they are handled in one CPU instruction. So you can avoid floating point math altogether.
--
Marco
More information about the Digitalmars-d-learn
mailing list