Hallo!
Habe gerade mal ein wenig mit einer Tabelle rumgespielt, die Zuordnungen von IPs zu Ländern enthält(bitte nicht über den Sinn dessen diskutieren ;-)), und zwar diese hier: http://ip-to-country.com/database/.
Wurde auf php.net erwähnt und ich fand es mal ganz interessant.
Habe mir die Tabelle runtergeladen, enthält 16.000 Datensätze, und zwar der Form:
"IP_FROM","IP_TO","COUNTRY_CODE","COUNTRY_NAME"
"406372352","406380543","US","UNITED STATES"
"406388736","406437887","CA","CANADA"
Die Felder:
IP_FROM NUMERICAL (DOUBLE) Beginning of IP address range.
IP_TO NUMERICAL (DOUBLE) Ending of IP address range.
COUNTRY_CODE CHAR(2) Two-character country code based on ISO 3166.
COUNTRY_NAME VARCHAR(50) Country name based on ISO 31
Ich dachte mir, 16.000 Zeilen müsste man doch eigentlich auch recht performant aus einer Flat-File auslesen können, also habe ich das (mit PHP, ich habe alle " aus den Daten entfernt) so versucht:
while (!feof($fp)) {
$buffer = fgets($fp , 72);
$ips = explode(',', $buffer, 2);
if($ips[0]<= $dec_ip && $dec_ip <= $ips[1]) {
$result = explode(',', $buffer, 4);
break;
}
}
Aber das war nicht wirklich schnell, hat im Schnitt gut 0,2 Sekunden gedauert. Und wie ich das bedeutend schneller bekommen soll weiß ich auch nicht. Ich habe auch versucht mit einem if, also nur prüfen ob 2. Nummer kleiner als meine Nummer, aber das war kaum schneller. Vielleicht liegt es auch am explode, aber so viel macht das wohl nicht.
Nur mal probehalber habe ich das mal mit mysql probiert, genau die Datentypen wie angegeben, einfach mit folgendem Kommando:
"SELECT COUNTRY_CODE, COUNTRY_NAME FROM ip WHERE IP_FROM <= '$dec_ip' && IP_TO >= '$dec_ip'"
Und alles in allem(mit Verbindung herstellen und einlesen in PHP) hat das ganze nur noch 0,04 Sekunden gedauert, es war fast 6 mal so schnell.
Dann habe ich mal den Typen von MyISAM auf HEAP verändert, da hat sich auch kaum was getan, lag vermutlich daran dass die Daten so kurz danach noch in irgendeinem Cache lagen.
Dann habe ich es mit einem Index versuchen wollen, aber egal was ich da probiert habe hat das ganze eher verlangsamt als es zu beschleunigen. Lässt sich hier einfach kein sinnvoller Index verwenden?
Aber was mich extremst wundert, wieso ist das auslesen von diesen paar Datensätzen um so viel langsamer ist als mit MySQL ohne jeglichen Index. Ich meine, wie bitte geht das dann mit dem Archiv hier performant?
Das beste was mir eingefallen war
$fp = fopen("ips.csv","r");
while (!feof($fp)) {
$buffer = fgets($fp , 21);
$com = strpos($buffer, ',');
if((INT) substr($buffer,$com+1,10) >= $dec_ip){
break;
}
}
Das hat ja nun wirklich nichts gemacht, aber es liegt immer noch über 0,2 Sekunden. Wieso?
Gibts irgendwelche "Tricks" sowas schneller zu machen?
Viele Grüße
Andreas
PS: wer "mitspielen" will ;-)
<?php
function start_timer($event) {
printf("timer: %s<br>\n", $event);
list($low, $high) = explode(" ", microtime());
$t = $high + $low;
flush();
return $t;
}
function next_timer($start, $event) {
list($low, $high) = explode(" ", microtime());
$t = $high + $low;
$used = $t - $start;
printf("timer: %s (%8.4f)<br>\n", $event, $used);
flush();
return $t;
}
$ip = $_GET['ip']; // 213.139.94.131
$num = explode(".", $ip);
$dec_ip = $num[0]*pow(256,3) + $num[1]*pow(256,2) + $num[2]*pow(256,1) + $num[3];
$t = start_timer("Start");
/* Version 1
$fp = fopen("ips.csv","r");
while (!feof($fp)) {
$buffer = fgets($fp , 21);
$com = strpos($buffer, ',');
if((INT) substr($buffer,$com+1,10) >= $dec_ip){
break;
}
}
fclose ($fp);
*/
/* Version 2
$fp = fopen("ips.csv","r");
while (!feof($fp)) {
$buffer = fgets($fp , 72);
$ips = explode(',', $buffer, 2);
if($ips[0]<= $dec_ip && $dec_ip <= $ips[1]) {
$result = explode(',', $buffer, 4);
break;
}
}
fclose ($fp);
*/
/* Version 3
mysql_connect("localhost", "user", "pass");
mysql_select_db("db");
$sql = "SELECT COUNTRY_CODE, COUNTRY_NAME FROM ip WHERE IP_FROM <= '$dec_ip' && IP_TO >= '$dec_ip'";
$res = mysql_query($sql);
$row = mysql_fetch_assoc($res);
echo $row['COUNTRY_NAME'];
*/
/*
Tabellenstruktur für Tabelle ip
CREATE TABLE ip (
IP_FROM double(10,0) NOT NULL default '0',
IP_TO double(10,0) NOT NULL default '0',
COUNTRY_CODE char(2) NOT NULL default '',
COUNTRY_NAME char(50) NOT NULL default ''
) TYPE=MyISAM;
*/
$t = next_timer($t, "Ende");
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<h2>locate IP</h2>
<form method="get" action="<?php echo $_SERVER['PHP_SELF']?>">
<p>IP:
<input type="text" name="ip" value="<?php echo $_GET['ip']?>">
<?php echo ($result[3]) ? $result[2].' ('.trim($result[3]).')' : 'NO DATA'; ?>
</p>
<p>
<input type="submit" value="abschicken">
</p>
</form>
</body>
</html>
PPS: bedenkt das ich bei der CSV-Version alle " entfernt habe.