I have a hash table which I would like to query as fast as possible. The hash table is stored this way :
When I query a hash, I extract first 4 characters then use it to find the file. For instance a hash starting by e0a458. I extract e0a4 and go to the file ./e/0/a4
On each file I have one line per hash[4].hash[5] couple, so 256 lines each one terminated by new line. I store the position of each of the 256 lines as offset on the first line.
- 8bytes offset of each line \n
- Data for 0x00 \n
- Data for 0x01 \n
- Etc...
Each word in each line is separated by 0x06 character (totally arbitrary).
Now, when I query a hash I look into the file, fseek the position of offset, read 8 characters, then fseek to that position, get the line, then go through each word, hash it and compare to query. At first I did that in PHP because I prefer it. Below is my code, running on PHP 8.1. Note that it isn't indented because I wrote it on nano on my server and it isn't really user friendly for indenting.
<?php
if(isset($argv[1]))
$hashes= fopen($argv[1],"r");
else
return 0;
while(!feof($hashes))
{
$hash = rtrim(fgets($hashes),"\n");
if($hash !== "")
{
$file= fopen($hash[0].'/'.$hash[1].'/'.$hash[2].$hash[3],'r');
fseek($file,hexdec($hash[4].$hash[5]) * 8);
$offset = fread($file,8);
fseek($file,$offset);
$dataLine = rtrim(fgets($file),"\n");
fclose($file);
$arr = explode("\x06",$dataLine );
foreach($arras as $word)
{
$md5 = md5($word);
if($md5 === $hash)
break;
}
}
}
fclose($file);
?>
I didn't find any more optimization to do on this code so I decided to move on to "faster" languages and I tried C and C++. The C version seem to run at same speed than PHP (but I don't really know how to code in C), while the C++ runs really slower than PHP. For a file containing 150,000 hashes, the PHP takes between 2 and 3s while the C++ one takes 45s. Here's the C++ version :
#include <iostream>
#include <fstream>
#include <string>
#include <boost/algorithm/string.hpp>
#include "md5.h"
int main(int argc, char *argv[])
{
if (argc < 2)
{
return 1;
}
std::ifstream hashes(argv[1]);
if (!hashes)
{
std::cout << "Could not open file: '" << argv[1] << "'\n";
return 1;
}
std::string hash;
std::string filePath = "./";
while(getline(hashes,hash)){
filePath += hash[0];
filePath += "/";
filePath += hash[1];
filePath += "/";
filePath += hash[2];
filePath += hash[3];
std::ifstream dataFile(filePath);
if(!dataFile){
std::cout << "Could not open data file\n";
return 1;
}
std::string firstOffset;
firstOffset += hash[4];
firstOffset += hash[5];
unsigned int x = std::stoul(firstOffset, nullptr, 16);
dataFile.seekg(x*8,std::ios_base::beg);
std::string offsetLine;
offsetLine.resize(8, ' ');
char* begin = &*offsetLine.begin();
dataFile.read(begin,8);
dataFile.seekg(std::stoul(offsetLine,nullptr,10),std::ios_base::beg);
std::string dataLine;
getline(dataFile,dataLine);
std::vector<std::string> result;
boost::split(result, dataLine, boost::is_any_of("\x06"));
int limit = result.size();
for (int i = 0; i < limit; i++){
if(md5(result[i]) == hash)
{
dataFile.close();
break;
}
}
filePath = "./";
}
return 0;
}
Now I had to learn C and C++ while writing this code so I guess it's not optimized since I cannot think of PHP as faster than C++.
Also for the sake of it, here's the C code :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>
#define MAXLENGTH 500000
int main (int argc, char *argv[])
{
if(argc > 1){
FILE* hashes;
hashes = fopen(argv[1], "r");
char hash[257];
char filePath[7];
char* sep = "/";
char totalFilePath[16];
while(fgets(hash,257,hashes) != NULL)
{
filePath[0] = hash[0];
filePath[1] = sep[0];
filePath[2] = hash[1];
filePath[3] = sep[0];
filePath[4] = hash[2];
filePath[5] = hash[3];
char offsetHexa[3];
offsetHexa[0] = hash[4];
offsetHexa[1] = hash[5];
strcat(strcpy(totalFilePath,"./"),filePath);
FILE* file;
file= fopen(totalFilePath,"r");
if ( file== NULL ) {
printf( "Cannot open file %s\n", totalFilePath );
exit( 0 );
}
else{
char offset[9];
unsigned char decimal = 0;
decimal = (unsigned char)strtoul(offsetHexa,NULL,16);
fseek(file,decimal*8,0);
fgets(offset,9,file);
char *line= malloc(MAXLENGTH);;
fseek(file,(int) strtol(offset,(char **)NULL,10),0);
fgets(line,MAXLENGTH,file);
char* word;
while( (word = strsep(&line,"\x06")) != NULL)
{
char md5word[33] = "";
int i;
char unsigned md5[MD5_DIGEST_LENGTH] = {0};
MD5((const unsigned char *)word, strlen(word), md5);
for (i=0; i < MD5_DIGEST_LENGTH; i++) {
sprintf(md5word + 2*i, "%02x", md5[i]);
}
if(strncmp(md5word,hash,32) == 0)
{
break;
}
}
}
fclose(file);
}
fclose(hashes);
}
return 0;
}
So I know this code will seem nooby to actual C/C++ developpers and it is. I have issues with string concatenation in C.
Would you have advises to improve C/C++ runtime ? I didn't find a way to split the data line without storing it in a vector, which doesn't seem fine to me.
Thank you for your enlightments and sorry for the code, I do that as a hobby.