最近我被分配了一些速度检测任务,以便我可以判断在将某个数量的行插入数据库时是更快使用php/php-cli还是c++。
在我们开始之前,让我告诉你一些细节,以便一切清楚明白:
- PHP 部分通过 Apache 直接在浏览器中请求运行。
- 进行硬盘驱动器测试的是 SSD 硬盘。我猜在普通硬盘上会更慢。这台机器本身没什么特别之处,大约六年左右。
- 所有插入操作都通过 prepared statements 完成。我们在 PHP 上使用 mysqli,而在 C++ 中使用 mysqlcppconn(由 Oracle 提供的 MySQL C++ 连接器)。
- 所有插入操作都逐个进行。我知道我们可以将它们堆叠起来,但是,我们正在进行测试。
- 时间是通过 PHP 中的 microtime 和 C++ 中的 header 显示的。
- 代码本身当然是不相等的。稍后会详细说明。
- 所有文本都采用 UTF-8 编码。里面有俄语、中文、阿拉伯语、西班牙语、英语和各种疯狂的东西。MySQL 表使用 utf8_4mb。
- C++ 代码中的数字是使用 std::vector 和 -O2 级别编译 g++ 的结果(向量优于映射、无序映射和 std::array)。
所以,这就是流程:
- 连接到数据库。
- 打开一个有N行的文本文件。
- 读取文件中的一行。
- 在分隔符上拆分该行。
- 使用拆分行的某些部分获取插入值(例如第0、1和3个索引)。
- 将这些部分发送到准备好的语句以进行插入。
- 重复此过程,直到完全读取文件。
两种代码都按预期工作。以下是结果数字:
php:
- 5000条目:1.42 - 1.27秒。
- 20000条目:5.53 - 6.18秒。
- 50000条目:14.43 - 15.69秒。
c++:
- 5000条目:1.78 - 1.81秒。
- 20000条目:7.19 - 7.22秒。
- 50000条目:18.52 - 18.84秒。
随着文件中的行数增加,PHP比C++表现更出色...起初,我怀疑是行分割函数的问题:在PHP中,使用"explode"进行分割。该算法对于C++来说非常简单...容器通过引用传递,并且其内容在运行时发生改变。容器只遍历一次。我确保容器“保留()”了所有必要的空间(记住,我最终选择了向量),这是固定的。容器在主函数中创建,然后通过引用传递到代码中。它从未被清空或调整大小:只有其内容发生改变。
template<typename container> void explode(const std::string& p_string, const char p_delimiter, container& p_result)
{
auto it=p_result.begin();
std::string::const_iterator beg=p_string.begin(), end=p_string.end();
std::string temp;
while(beg < end)
{
if( (*beg)==p_delimiter)
{
*(it)=temp;
++it;
temp="";
}
else
{
temp+=*beg;
}
++beg;
}
*(it)=temp;
}
如前所述,执行的任务是相同的,但生成它的代码不同。C++代码具有通常用于控制mysql交互的try-catch块。至于其余部分,主循环运行直到达到EOF,并且每次迭代都会检查插入是否失败(在c++和php中都是如此)。
我曾看到c++在处理文件及其内容方面表现得比php更出色,因此我希望在这里也能适用。不知何故,我怀疑分割算法,但也许只是数据库连接器较慢(尽管当我禁用数据库交互时,php仍然处理得更快),或者我的代码不够好...
就性能分析而言,gprof对c++代码产生了以下输出:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
60.00 0.03 0.03 50000 600.00 600.00 void anc_str::explotar_cadena<std::vector<std::string, std::allocator<std::string> > >(std::string const&, char, std::vector<std::string, std::allocator<std::string> >&)
40.00 0.05 0.02 insertar(sql::PreparedStatement*, std::string const&, std::vector<std::string, std::allocator<std::string> >&)
0.00 0.05 0.00 1 0.00 0.00 _GLOBAL__sub_I__ZN7anc_str21obtener_linea_archivoERSt14basic_ifstreamIcSt11char_traitsIcEE
在这里,“explotar_cadena”是“explode”,“insertar”是“拆分此行并设置预处理语句”。正如您所看到的,有60%的时间花在那里(不足为奇......它运行了50000次并进行了这种疯狂的拆分操作)。"obtener_linea_archivo"只是“请将下一行转储到字符串中”。
没有mysql交互(只需加载文件,读取行并拆分它们),我得到了以下测量结果: php - 5000个条目:0.019-0.036秒。 - 20000个条目:0.09-0.10秒。 - 50000个条目:0.14-0.17秒。
c ++
- 5000个条目:0.07-0.10秒。 - 20000个条目:0.25-0.26秒。 - 50000个条目:0.49-0.55秒。
好的,两种方法的时间都很好,对于实际生活来说几乎不可感知,但我很惊讶...因此问题在于:我应该期望这种情况吗?任何有先前经验的人愿意帮忙吗?
希望您能提前感谢。
编辑:这里有一个简化版本的快速链接,包含输入文件、C++代码和php代码 [ http://www.datafilehost.com/d/d31034d6 ]。请注意,此处没有SQL交互:只有文件打开、字符串分割和时间测量。请原谅代码和注释中出现的语法错误和一半的西班牙语变量名,因为这是匆忙完成的。此外,请注意上面的gprof结果:我不是专家,但我认为我们正在尝试找到更好的字符串分割方法。