Optymizowanie parsera liczb

0

Piszę bibliotekę która m.in. pobiera liczby z tekstu i konwertuje je do postaci double. Po kilku benchmarkach doszedłem do takiej, jak na razie najszybszej wersji (funkcja parseinteger):

double __forceinline parsehex(TCHAR *&text)
{
	unsigned int hexval = 0;
	int pos = 0;
	// pobierz maksymalnie osiem znaków, ale przerwij na pierwszym nieszesnastkowym znaku
	while (ISTYPE(*text, ISTYPE_HEX) && (pos < 8)) // 1234567890ABCDEFabcdef
	{
		hexval = hexval<<4 | hexlookuptable[*text];
		text++;
		pos++;
	}
	return (double)hexval;
}

// oblicza wartość ułamka. Zmienna text powinna wskazywać pierwszą cyfrę po przecinku
double __forceinline parseulamek(TCHAR *&text)
{
	unsigned int ulamek = 0;
	int pos = 0;
	while (ISTYPE(*text, ISTYPE_NUM) && (pos < 9))
	{
		ulamek = ulamek * 10 + hexlookuptable[*text];
		text++;
		pos++;
	}
	// a resztę cyfr ułamka zignoruj
	while (ISTYPE(*text, ISTYPE_NUM))
	{
		text++;
	}
	return (double)ulamek / exponentmap[pos];
}


double __forceinline parseinteger(TCHAR *&text)
{
	double result;
	int pos = 0;
	// to jest liczba - jeżeli zaczyna się od 0x to skonwertuj do postaci dziesiętnej
#ifdef UNICODE
	if ((*(int*)text == ('x'<<16 | '0')) && ISTYPE(*(text+2), ISTYPE_HEX)) // 1234567890ABCDEFabcdef
#else
	if ((*(short*)text == 'x0') && ISTYPE(*(text+2), ISTYPE_HEX)) // 1234567890ABCDEFabcdef
#endif
	{
		text+=2;
		result = parsehex(text);
	}
	else
	{
		// to jest liczba dziesiętna - pobierz maksymalnie 9 znaków by nie przepelnic int'a.
		// Jeżeli liczba będzie miała więcej cyft, to zaczniemy używać double do skompletowania jej.
		// TODO: dodaj obsługę exponentu

		unsigned int calkowita = 0;
		while (ISTYPE(*text, ISTYPE_NUM) && (pos < 9))
		{
			calkowita = calkowita * 10 + hexlookuptable[*text];
			text++;
			pos++;
		}

		if (*text == '.') // liczba z przecinkiem
		{
			// pomiń przecinek i tym samym sposobem wczytaj część ułamkową
			text++;
			result = (double)calkowita + parseulamek(text);
		}
		else if (ISTYPE(*text, ISTYPE_NUM))
		{
			// liczba jest dluzsza niz 9 znakow, przskocz z int na double
			result = (double)calkowita;
			while (ISTYPE(*text, ISTYPE_NUM))
			{
				result = result * 10 + hexlookuptable[*text];
				text++;
				pos++;
			}
			if (*text == '.') // liczba z przecinkiem
			{
				// pomiń przecinek i tym samym sposobem wczytaj część ułamkową
				text++;
				result += parseulamek(text);
			}
		}
		else // koniec liczby całkowitej
		{
			result = (double)calkowita;
		}
	}
	return result;
}

Tablice użyte tutaj mają za zadanie przyspieszenie obliczania, np.

  • hexlookuptable[] na pozycji cyfry '0' ma (int)0, na pozycji 'A' oraz 'a' jest (int)10;
  • ISTYPE to makro używjące tablicy typemap, która przypisuje kolejne znaki ascii do grupy {cyfra, liczba, hex, ...} - coś w rodzaju isdigit, isascii...
    Wszystkie tabele umieściłem tu http://sapero.pastebin.com/f40cb6e81

Macie lepszy pomysł by to zoptymalizować? Uprzedzam, że gotowe funkcje typu atof, sscanf są o wiele wolniejsze. Sprawdziłem chyba wszystkie z crt.</cpp>

0

forceinline nie zawsze jest dobrym pomyslem.

jesli petla w ktorej uzywamy parseinteger nie miesci sie w cache poziomu 0 kodu to lepiej nie robic inline. ladowanie kodu zajmuje zawsze wiecej niz jego wykonywanie. a wiec jezeli bez inline petla wraz z funkcja sie miesci w cache kodu poziomu 0 a z inline nie (oczywiscie biorac pod uwage asocjacyjnosc cache) to lepiej nie stosowac inline (kompilator powinien sie sam zorientowac).

ps. poczytaj o automatach skonczonych. moga sie tutaj przydac. lex program do parsowania jezykow uzywa wlasnie najczesciej automatow skonczonych.

0

@up: on dał __forceinline, czyli kompilator nie ma nic do gadania. Jeśli zmieni na inline to wtedy kompilator mógłby zrobić zwykłą funkcję.

1 użytkowników online, w tym zalogowanych: 0, gości: 1