Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C: Alkeisesimerkki suoritusnopeuden mittauksesta

Pekka Karjalainen [23.11.2008 15:43:30]

#

Esitän lyhyen ohjelman, jolla voi testata muistiosoituksien järjestyksen merkitystä ohjelman nopeuteen. Tämä tulee ikään kuin jatkona keskustelulle mittauksen tärkeydestä.

Yleensä mittaukset kannattaa suorittaa kääntäjän ohessa olevilla profilointityökaluilla. Yksinkertainenkin keino riittää, jos tietää varoa pahimpia mittausvirheitä ja -harhoja aiheuttavia seikkoja. Tässä ohjelmassa on käytetty C-kielen clock()-funktiota, joka mittaa ns. oikeasti kuluvan ajan. Joskus kone voi olla enemmän kuormitettu, joskus vähemmän, mikä vaikuttaa sen antamiin tietoihin.

Mutta ajamalla saman testin useasti voi laskea tulosten keskiarvon ja -hajonnan. Sen ja ihmeellisen tilastotieteen avulla voi saada käsityksen siitä, kuinka luotettavia mittaustulokset ovat. Jos selvästi näkee, että toinen ratkaisu on aina suhteelliesti parempi kuin toinen, sekin auttaa pitkälle.

Ohjelmassa käsitellään isoja matriiseja. Ensimmäinen testi hakee matriisista suurimman arvon ensin käyden sen läpi riveittäin ja sitten toisessa funktiossa sarakkeittan. Testi toistetaan monesti ja mittaustulos on näiden yhteensä viemä aika. Näin kannattaa yleensä tehdä, koska saman asian toistaminen monta kertaa tasoittaa välimuistin aiheuttamaa vaikutusta ensimmäisen testin nopeuteen (harjoitustehtävä: ota selvää miksi).

Toinen testi on matriisitulo. Tähän löytyy kaava vaikkapa Wikipediasta. Ohjelma suorittaa tulon ensin suoraan määritelmän mukaan ja sitten erityisellä tavalla jaettuna pienempiin blokkeihin. Jälkimmäinen tapa on monimutkaisempi ja se vaatii, että tulosmatriisi tyhjennetään erikseen. Silti saattaa käydä niin, että se osoittautuu nopeammaksi (harjoitustehtävä 2: ota nyt ihmeessä selvää, miksi).

Ohjelmaa kääntäessä voi komentoriville liittää erinäisiä optioita, jotka vaikutavat ohjelmat toimintaan. Ne löytyvät koodista #ifdef-riveistä. Esim. gcc -DROWS=1024 -DCOLS=1024 käyttäisi näin isoa matriisia testeihin. Se, että ohjelma vaatii uudelleenkääntämisen, kun parametreja muutetaan, tekee siitä selkeämmän. Vähän vaikeammin ohjelmoimalla olisi toki voinut erikokoiset testit ympätä yhteen ohjelmaan -- kuitenkin lyhyt ohjelma on nopea kääntää.

Koodissa valmiina olevat arvot on valittu vähän siihen suuntaan, että testi kulkisi läpi alle 2:ssa minuutissa ja tuottaisi jänniä tuloksia koneella, jossa on 1,6 GHz:n prosessori. Hitaamman käyttäjä voinee vähentää testien määrää. Kannattaa myös kokeilla eri taulukon kokoja, sekä eri optimointivalintoja.

Tämän on tarkoitus inspiroida aloittelevia ohjelmoijia miettimään, että se tiedon esitystapa muistissa on tärkeä (tietorakenteet), sekä se, miten se tieto haetaan sieltä (välimuistikoherenssi). Toivottavasti siitä on jotain hupia myös kokeneemmille.

Matriisitulo pitäisi voida optimoida ainakin reilut kaksi kertaa tätä ohjelmaa nopeammaksi vastaavan kokoisilla taulukoilla. Pointti ei kuitenkaan ole nopea tulo, vaan erot kahden eri ratkaisun välillä, jotka todellakin voivat osoittautua aika mrekittäviksi.

Kaikin puolin koodi ei sisällä aivan ihanteellista koodaustyyliä :) (se on vanha oppikirjaesimerkki, jonka sovelsin uudelleen)

Erityisesti makron määrittelyt funktion sisällä eivät tarkoita, että ne on rajattu sinne. Yksi #define on vain siellä missä on, koska se on lähellä käyttöpaikkaa.

#include <assert.h> /* assert */
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit, system */
#include <string.h> /* memset */
#include <time.h> /* clock, clock_t */

#ifndef ROWS
#define ROWS 512
#endif

#ifndef COLS
#define COLS 512
#endif

#ifndef BLOCKSIZE
#define BLOCKSIZE 128
#endif

#ifndef SEARCHTESTS
#define SEARCHTESTS 1000
#endif

#ifndef MULTESTS
#define MULTESTS 10
#endif

const double neg_inf = 0.0 / -1.0;

void print_matrix(double mat[ROWS][COLS]) {
	int i, j;
	for (i=0; i<ROWS; ++i) {
		for (j=0; j<COLS; ++j)
			printf ("%f ", mat[i][j]);
		printf ("\n");
	}
	printf ("\n");
}

void fill_matrix1(double mat[ROWS][COLS]) {
	int i, j;
	for (i=0; i<ROWS; ++i)
	for (j=0; j<COLS; ++j)
		mat[i][j]=1.0/((i+j)%10 + 1);
}

void fill_matrix2(double mat[ROWS][COLS]) {
	int i, j;
	for (i=0; i<ROWS; ++i)
	for (j=0; j<COLS; ++j)
		mat[i][j]=2.0/((i*j)%10 + 1);
}


void zero_matrix(double mat[ROWS][COLS]) {
	memset(mat, 0, sizeof(double) * ROWS * COLS);
}

double find_max1(double mat[ROWS][COLS]) {
	int i, j;
	double best = neg_inf;
	for (i=0; i<ROWS; ++i)
	for (j=0; j<COLS; ++j) {
		if (mat[i][j] > best) best = mat[i][j];
	}
	return best;
}

double find_max2(double mat[ROWS][COLS]) {
	int i, j;
	double best = neg_inf;
	for (j=0; j<COLS; ++j)
	for (i=0; i<ROWS; ++i) {
		if (mat[i][j] > best) best = mat[i][j];
	}
	return best;
}

void matrix_mul1(double left[ROWS][COLS],
	double right[ROWS][COLS], double res[ROWS][COLS])
{
	int i, j, k;
	double e;
	const int N = ROWS;
	assert (ROWS==COLS);
	for (i=0; i<N; ++i)
	for (j=0; j<N; ++j) {
		e = 0;
		for (k=0; k<N; ++k)
			e += left[i][k] * right[k][j];
		res[i][j] = e;
	}
}

void matrix_mul2(double left[ROWS][COLS],
	double right[ROWS][COLS], double res[ROWS][COLS])
{
	int i, j, k;
	double e;
	int jb, kb;
	const int N = ROWS;
	#define MIN(x,y) ((x) < (y) ? (x) : (y))
	assert (ROWS==COLS);
	zero_matrix(res);
	for(jb=0; jb<N; jb += BLOCKSIZE)
	for(kb=0; kb<N; kb += BLOCKSIZE)
	for (i=0; i<N; ++i)
	for (j=jb; j<MIN(jb+BLOCKSIZE, N); ++j) {
		e = 0;
		for (k=kb; k<MIN(kb+BLOCKSIZE, N); ++k)
			e += left[i][k] * right[k][j];
		res[i][j] += e;
	}
}


int main (void) {
	static double
		mat1[ROWS][COLS],
		mat2[ROWS][COLS],
		mat3[ROWS][COLS],
		mat4[ROWS][COLS];
	int i, j;
	double max1, max2;
	clock_t start, stop;
	fill_matrix1(mat1);
	fill_matrix2(mat2);

	printf ("Test 1: Matrix search rows first.\n");
	start = clock();
	for (i=0; i<SEARCHTESTS; ++i)
		max1 = find_max1(mat1);
	stop = clock();
	printf ("Test 1 took approximately %f seconds.\n",
		((double)(stop-start)) / CLOCKS_PER_SEC);

	printf ("Test 2: Matrix search columns first. \n");
	start = clock();
	for (i=0; i<SEARCHTESTS; ++i)
		max2 = find_max2(mat1);
	stop = clock();
	printf ("Test 2 took approximately %f seconds.\n",
		((double)(stop-start)) / CLOCKS_PER_SEC);

	if (max1 != max2) {
		printf ("!error: program found different maximums from same matrix");
		exit (1);
	}

	if (ROWS != COLS) {
		printf ("Can't test matrix multiplication. Dumb program "
			"expects square matrices. Sorry.\n");
		exit(0);
	}

	printf ("Test 3: Straight matrix multiplication. \n");
	start = clock();
	for (i=0; i<MULTESTS; ++i)
		matrix_mul1(mat1, mat2, mat3);
	stop = clock();
	printf ("Test 3 took approximately %f seconds.\n",
		((double)(stop-start)) / CLOCKS_PER_SEC);

	printf ("Test 4: Matrix multiplication in blocks of size %d. \n", BLOCKSIZE);
	start = clock();
	for (i=0; i<MULTESTS; ++i)
		matrix_mul2(mat1, mat2, mat4);
	stop = clock();
	printf ("Test 4 took approximately %f seconds.\n",
		((double)(stop-start)) / CLOCKS_PER_SEC);

	for (i=0; i<ROWS; ++i)
	for (j=0; j<COLS; ++j) {
		double diff = mat3[i][j] - mat4[i][j];
		diff = (diff < 0.0) ? -diff : diff;
		if (diff > 0.00001) {
			printf ("!error: diff at %d %d\n", i, j);
			printf ("%f %f\n", mat3[i][j], mat4[i][j]);
			exit(1);
		}
	}
	/* only for use with small matrices
	print_matrix(mat1);
	print_matrix(mat2);
	print_matrix(mat3);
	print_matrix(mat4);
	*/
	/* ring bell (\a) to signal end of long test */
	#ifdef ALERT_FINISHED
	printf ("Done\a\n");
	#endif

	/* prevent IDE from closing console window; for Windows people */
	#ifdef USE_PAUSE
	system("PAUSE");
	#endif

	return 0;
}

ville-v [23.11.2008 17:32:53]

#

Tarkoitit kai tätä koodivinkiksi? Näyttäisi kuitenkin olevan keskustelussa.

Pekka Karjalainen [23.11.2008 20:44:16]

#

Ajattelin, ettei tällainen huolimattomasti tehty pikku ohjelma koodivinkiksi kelpaa. Lisäksi ohjelmassa on virhe. Ääretön ei ole ääretön, koska kirjoitin jakolaskun tyhmyyksissäni väärinpäin. Varmaan sen saisi vakiona jostain float.h:sta, mutta C-manuaali on tuolla metrien päässä toisessa huoneessa :(

Sinänsä olisi varmasti hyvä idea tehdä jotakin tämän aiheen mukaisia koodivinkkejä.

Metabolix [23.11.2008 21:12:28]

#

Kannattaa myös muistaa, että Windowsin clock-funktio on rikki.

Ääretön löytyy float-tyyppisenä vakiosta INFINITE ja kaikkina kolmena liukulukutyyppinä vakioista HUGE_VAL, HUGE_VALF ja HUGE_VALL.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta