Winuser's Blog

11 grudnia 2011

część II : Logicznie błędny kod – PHP

Filed under: PHP,Programowanie — winuser @ 15:11
Tags: ,

Wpis jest kontynuacją poprzedniego postu o błędach logicznych w kodzie programu. Ponieważ ostatnio zmuszony byłem do ostrego siedzenia nad kodem w języku PHP, przez pewnego klienta, dlatego wpis będzie właśnie traktował o tym języku. Okazuje się, że w PHP niewiele trzeba aby pisać kod logicznie błędny, jednocześnie trudny do poprawienia, szczególnie gdy projekt jest większy niż te kilkaset linijek.
Na początek klasyka gatunku pod tytułem „błąd z pętlą” :

while (cos_tam);
{
 // jakis kod
 int a = 1 + 2;
}

Klasyka tym razem dla operatora równości :

if ($nextRowID = $rowID)
 break;

Jak widać, tym razem programista tak się śpieszył, że napisał tylko jeden znak „=”, w wyniku czego wyszedł mu operator przypisania. Efektem tego będzie zawsze true, czyli warunek jest bezsensowny.

Programiści języków z rodzaju C/C++ automatycznie dodają znak średnika na końcu wiersza. Zdarza im się to nawet poza kodem.
Kolejny przykład, jeden z moich faworytów. Przykład typowy dla języka PHP w którym zmienna tworzona jest w miejscu pierwszego użycia :

$installler[$row['ID']] = $row['value'];
// kilkaset linii dalej lub w innym pliku
echo $installer['1'];

Dla tych którzy jeszcze nie zauważyli co z powyższym kodem jest źle, już śpieszę z wytłumaczeniem. Chodzi o potrójną literkę `l` w nazwie zmiennej. W wyniku tej pomyłki interpreter PHP utworzy dwie zmienne, jedną $installler oraz drugą $installer. Oczywiście w $installer nie będzie tego co chcieliśmy osiągnąć w linii numer 1 🙂
Czas na przejście do kolejnego przykładu. Jest on również typowy dla PHP. Typowy, ponieważ w języku C kompilator rzuciłby warning „not all paths return a value”.

function Worker($arg_1)
{
 if ($arg_1 < 1024)
 return false;
}

function Another()
{
 // gdzieś w innym, odległym miejscu..
 $result = Worker(300);
 if ($result == true)
 {
 // nigdy się nie wywoła..
 }
}

Ten błąd akurat można w całkiem prosty sposób wykryć, wystarczy zajrzeć w ciało funkcji „Worker”.
Kolejny przykład dotyczy zapytania MySQL. Z bazami danych programista PHP ma doczynienia praktycznie ciągle, więc przykład również się nadaje.

$ret = $db->query("SELECT * FROM `usr` WHERE `uid` = '$uid' AND `field` = $var;");
if ($ret == false || $ret->num_rows == 0)
{
 echo "error";
}

Co będzie wynikiem tego zapytania gdy zmienna $var nie będzie miała wartości nadanej ? Zapytanie będzie błędne. Rozwiązaniem jest branie wszystkiego co możliwe, w cudzysłowy pojedyncze, czyli ostatni kawałek zapytania wyglądałby :

AND `field` = '$var';

Jak widać PHP jest bardzo podatny na błędy w logice kodu. Dzieje się tak w dużej mierze z tego powodu że nie ma on określonych na sztywno typów danych, czyli zmienna która przed chwilą przechowywała tekst, za chwilę może już przechowywać liczbę typu double albo zmienną typu bool. Ta właściwość może być błogosławieństwem albo zmorą. Być może ja osobiście mam takie problemy podczas programowania w PHP, ponieważ jestem dość silnie związany z C / C++ w których to językach jak wiadomo panuje dość silna typizacja zmiennych. Dodatkowym powodem problemów jest to że interpreter PHP nie zwraca uwagi czy funkcja zwraca jakąś wartość, czy może zwraca tylko czasami, ponieważ nie istnieje prototyp funkcji do której kod musi być dopasowany (czyli że funkcja zwraca typ bool i koniec). Na dłuższą metę programując w PHP chyba być zwariował 😉

18 listopada 2010

C++ kontra PHP

Filed under: C++,PHP,Programowanie,Projekty,WWW — winuser @ 9:42
Tags: , , , , ,

Tak jak ostatnio obiecałem, tak oto zamieszczam moje przemyślenia / badania kwesti wydajności programów kompilowanych (C++) oraz skryptów (PHP). Jest sprawą oczywistą że walka jest nierówna, że porównanie może nie mieć sensu, że te języki stworzono do innych zadań. Ale olejmy na chwilę te wszystkie gadki, jak naprawdę przekłada się to co mówią ludzie na prawdziwe pomiary ? Sprawdziłem to i w tym wpisie można przeczytać efekty tego. Zamieszczam również kod klasy C++ do mierzenia czasu wykonania funkcji czy bloku programu. Zasady sprawdzania były proste, postawiony serwer www z użyciem popularnego pakietu WebServ (do testowania skryptu PHP) oraz skompilowany program C++ (użyłem kompilatora G++) w wersji release wrzucony do folderu CGI-BIN, odpalany z poziomu przeglądarki internetowej Opera. Na pierwszy ogień poszła konktatencja napisów. Żeby walka była wyrównana (zresztą nie może i tak być o wyrównanej walce mowy), to w kodzie C++ użyłem klasy std::string zamiast zwykłych wskaźników char, bo wynik mógłby się okazać wręcz miażdżący dla PHP. Niestety nawet mimo tego się taki okazał.. Wynik PHP: ~27 sekund, wynik C++: ~1.8 sekundy. Kod programu C++ :

#include <iostream>
#include <string>

#include "PerformanceTimer.h"

using namespace std;

int main()
{
	cout << "Content-type: text/html" << endl << endl;
	cout << "<html><head><title>Obliczanie</title>";
	cout << "</head><body>" << endl;
	cout << "<h1>Wyniki obliczen</h1>";
	
	PerformanceTimer pt;
	string napis;
	
	pt.Start();
	for (int i = 0; i < 100000000; i++)
	{
		napis += i;
	}
	pt.Stop();
	
	cout << (pt.GetTime() / (double)10000000) << endl;
	cout << "</body></html>";
	
	return 0;
}

Skrypt PHP:

<html>
	<head>
		<title>Obliczanie</title>
	</head>
	<body>
		<h1>Wyniki obliczen</h1>
		<?php
			$starttime = microtime(true);
			for ($i = 0; $i < 100000000; $i++)
			{
				$napis += $i;
			}
			$stoptime = microtime(true);
			
			echo round($stoptime - $starttime, 5);
		?>
	</body>
</html>

Wykonywanie kodu tego typu (tak wielka ilość iteracji) nie zdarza się raczej zbyt często, aczkolwiek daje pewien obraz sytuacji. W pierwszym teście to, który wygra dość łatwo można było przewidzieć. Ale co gdybyśmy chcieli potestować odczyt i zapis do pliku ? Sprawdziłem. Tym razem testowałem trzy rzeczy, pierwsza odczyt plików i zapis w PHP, druga to odczyt i zapis z użyciem strumieni C++, ostatnia rzecz którą testowałem to odczyt oraz zapis pliku z użyciem funkcji API systemu Windows. Wyniki tym razem nie są już aż tak miażdżące, lecz nadal odrazu widać który sposób jest wydajniejszy. W celu lepszego zobrazowania sytuacji stworzyłem wykres. Jest on wyskalowany w sekundach. Rozmiar pliku na którym testowane były operacje zapis / odczyt można w prosty sposób obliczyć: rozmiar_bufora * ilosc_iteracji = ~ 125 MB.

Dołączam równiez kod C++ użyty do odczytu / zapisu Win32 :

#include <iostream>
#include <string>

#include "PerformanceTimer.h"

using namespace std;

int main()
{
	cout << "Content-type: text/html" << endl << endl;
	cout << "<html><head><title>Obliczanie</title>";
	cout << "</head><body>" << endl;
	cout << "<h1>Wyniki obliczen</h1>";
	
	PerformanceTimer pt;
	DWORD dwWrite;
	
	HANDLE hFile = CreateFile("plik.bin",
							  GENERIC_READ | GENERIC_WRITE,
							  0,
							  0,
							  CREATE_ALWAYS,
							  0,
							  0);
	char szBuffer[255];
	memset(&szBuffer, 0, sizeof(szBuffer));
	
	pt.Start();
	for (int i = 0; i < 500000; i++)
	{
		WriteFile(hFile, szBuffer, sizeof(szBuffer), &dwWrite, 0);
	}
	pt.Stop();
	
	cout << (pt.GetTime() / (double) 10000000) << endl;
	CloseHandle(hFile);
	
	hFile = CreateFile("plik.bin",
					   GENERIC_READ | GENERIC_WRITE,
					   0,
					   0,
					   OPEN_EXISTING,
					   0,
					   0);
	
	pt.Start();
	for (int i = 0; i < 500000; i++)
	{
		ReadFile(hFile, szBuffer, sizeof(szBuffer), &dwWrite, 0);
	}
	pt.Stop();
	
	cout << (pt.GetTime() / (double) 10000000) << endl;
	CloseHandle(hFile);
	
	cout << "</body></html>";
	return 0;
}

Kod użyty do odczytu / zapisu z użyciem strumieni :

#include <iostream>
#include <fstream>
#include <string>

#include "PerformanceTimer.h"
		
using namespace std;

int main()
{
	cout << "Content-type: text/html" << endl << endl;
	cout << "<html><head><title>Obliczanie</title>";
	cout << "</head><body>" << endl;
	cout << "<h1>Wyniki obliczen</h1>";
	
	PerformanceTimer pt;
	char szBuffer[255];
	ofstream file;
	
	memset(&szBuffer, 0, sizeof(szBuffer));
	file.open("plik.bin");
	
	pt.Start();
	for (int i = 0; i < 500000; i++)
	{
		file << szBuffer;
	}
	pt.Stop();
	
	file.close();
	cout << (pt.GetTime() / (double) 10000000) << endl;
	
	ifstream file_2("plik.bin");
	
	pt.Start();
	for (int i = 0; i < 500000; i++)
	{
		file_2.read(szBuffer, sizeof(szBuffer));
	}
	pt.Stop();
	
	file_2.close();
	cout << (pt.GetTime() / (double) 10000000) << endl;

	cout << "</body></html>";
	return 0;
}

A to kod dla PHP :

<html>
	<head>
		<title>Obliczanie</title>
	</head>
	<body>
		<h1>Wyniki obliczen</h1>
		<?php
			for ($i = 0; $i < 255; $i++) $buffer .= "6";
			
			$file = fopen("plik.bin", "w");
			flock($plik, LOCK_EX);
			
			$starttime = microtime(true);
			for ($i = 0; $i < 500000; $i++)
			{
				fwrite($file, $buffer);
			}
			$stoptime = microtime(true);
			
			flock($file, LOCK_UN);
			fclose($file);
			echo round($stoptime - $starttime, 5) . "<br/>";
			
			$file = fopen("plik.bin", "r");
			flock($file, LOCK_SH);
			
			$starttime = microtime(true);
			for ($i = 0; $i < 500000; $i++)
			{
				fread($file, 255);
			}
			$stoptime = microtime(true);
			
			echo "<br/>" . round($stoptime - $starttime, 5) . "<br/>";
			flock($file, LOCK_UN);
			fclose($file);
		?>
	</body>
</html>

Na koniec mała konkluzja tego testu. Od początku wierzyłem że lepsze wyniki uzyska program pisany w C++ i tak też się okazało. Lecz żeby było ciekawiej, to dodam, nie zawsze wyniki uzyskiwane były tak dobre. Nie wiem od czego to zależy, w każdy razie program uruchamiany osobno (nie poprzez serwer www na żadanie przeglądarki) uzyskiwał stabilniejsze i krótsze czasy. Czasem nawet o 1/3. A w przypadku zapisu do pliku wyniki okazywały się być nawet gorsze (!). Mimo tego jednak uważam, że używanie programów CGI kompilowanych może znacząco podnieść wydajność treści dynamicznej, aczkolwiek jest bardziej złożone niż zwykły skrypt w PHP. Ten test nie miał za zadanie oczywiście w żaden sposób pokazać wyższości jednego języka nad drugim. Obydwa lubię i tak już zostanie. Potraktujcie ten test raczej jako ciekawostkę, bo raczej chyba nikt nie będzie pisał www z użyciem C++.
Kod klasy PerformanceTimer można pobrać z pod tego adresu : http://sourceforge.net/projects/performancetime/files/PerformanceTimer.cab/download Jest to klasa napisana z użyciem kodu zawartego w książce „Windows via C/C++” i ja ten kod opakowałem w klasę aby było wygodniej.