Virhe johtuu funktiosta Square::getPrevious():
int* Square::getPrevious(){
int table [2];
int* pointer;
table[0]=previousX;
table[1]=previousY;
pointer=table;
return pointer;
}
int table[2] on olemassa pinossa vain funktion suoritusaikana, mutta funktio palauttaa osoittimen siihen. Sitten kun kutsuvassa ohjelmassa yritetään käyttää taulukkoa, osoitin osoittaa olemattomaan sijaintiin (käytännössä juuri kutsuvan funktion pinon ulkopuolelle).
Ongelman voisi korjata muuttamalla int table[2]:n staattiseksi (static int table[2], jolloin se on jatkuvasti olemassa, mutta tämä on rumaa ja virhealtista. Tyylikkäämpää ja helpompaa olisi vain tehdä erilliset getPreviousX ja getPreviousY -funktiot, jotka palauttavat koordinaatit erikseen:
inline int Square::getPreviousX() {
return this->previousX;
}
inline int Square::getPreviousY() {
return this->previousY;
}
Tällä muutoksella, sekä käyttäen C++-kääntäjien kanssa optimointitasoa -O3, suoritusajat ovat koneellani:
G++ 4.6.3: 2.34 s
Clang++ 3.3: 2.17 s
OpenJDK 7: 1.98 s
Funktiokutsujen optimointiC++-koodin ongelmana on Square-luokan funktioiden kutsumisesta aiheutuva overhead. Ilmeisesti Javassa se on huomattavasti vähäisempi, tai kääntäjä pystyy välttämään osan erillisistä metodikutsuista kopioimalla metodien koodin kutsuvaan ohjelmaan (inline). C++:ssa tämän saa aikaan siirtämällä koko Square-luokan koodin funktioineen samaan tiedostoon kutsuvan funktion kanssa, jolloin kääntäjä inline-optimoi kutsut automaattisesti (ainakin optimointitasoilla -O2 ja -O3).
Funktiot voi myös siirtää square.cpp-tiedostosta square.h-tiedostoon, jolloin koodi tulee osaksi main.cpp-käännösyksikköä esiprosessointivaiheessa. Tässä on huomattava, että jos sama square.h sisällytetään moneen eri .c/.cpp-tiedostoon, koodi monistuu useaan paikkaan myös lopullisessa binäärissä ja ajon aikana esimerkiksi staattisista muuttujista on yhtä monta versiota kuin koodista.
Suoritusajat inline-optimoinnin jälkeen:
G++ 4.6.3: 0.99 s
Clang++ 3.3: 0.85 s
LisäoptimointiaJava vs C/C++ (tai JIT vs. staattinen kääntäjä) on aina mielenkiintoinen aihe, joten tein ohjelmasta vielä perinteisen, jossain määrin optimoidun
C-toteutuksen ja siitä suoran
Java-porttauksen.
C-version käännös:
gcc -Wall -O3 -o knightstour.o -c knightstour.c
gcc -Wall -O3 -o knightstour knightstour.o
("gcc":n voi tässä korvata komennolla "clang", jos Clang-kääntäjä on asennettuna)
Java-version käännös:
javac KnightsTour.java
GCC:llä käännetyn version suoritus:
time ./knightstour 6 1 1
-- 15 -- -- 34 -- -- 29 -- -- 22 -- -- 17 -- -- 36 --
-- 30 -- -- 1 -- -- 16 -- -- 35 -- -- 28 -- -- 21 --
-- 33 -- -- 14 -- -- 31 -- -- 20 -- -- 23 -- -- 18 --
-- 8 -- -- 11 -- -- 2 -- -- 25 -- -- 4 -- -- 27 --
-- 13 -- -- 32 -- -- 9 -- -- 6 -- -- 19 -- -- 24 --
-- 10 -- -- 7 -- -- 12 -- -- 3 -- -- 26 -- -- 5 --
real 0m0.531s
user 0m0.528s
sys 0m0.000s
Java-version suoritus:
time java KnightsTour 6 1 1
-- 15 -- -- 34 -- -- 29 -- -- 22 -- -- 17 -- -- 36 --
-- 30 -- -- 1 -- -- 16 -- -- 35 -- -- 28 -- -- 21 --
-- 33 -- -- 14 -- -- 31 -- -- 20 -- -- 23 -- -- 18 --
-- 8 -- -- 11 -- -- 2 -- -- 25 -- -- 4 -- -- 27 --
-- 13 -- -- 32 -- -- 9 -- -- 6 -- -- 19 -- -- 24 --
-- 10 -- -- 7 -- -- 12 -- -- 3 -- -- 26 -- -- 5 --
real 0m0.843s
user 0m0.848s
sys 0m0.004s
Kaikki suoritusajat (parametrit 6 1 1):
GCC 4.6.3: 0.53 s
Clang 3.3: 0.49 s
OpenJDK 7: 0.84 s
Suoritusajat vaikeammasta lähtöpisteestä (parametrit 6 1 0):
GCC 4.6.3: 150 s
Clang 3.3: 137 s
OpenJDK 7: 213 s