pybind11 c++ unordered_map 比 python dict 慢 10 倍?
pybind11 c++ unordered_map 10x slower than python dict?
我将 c++ unordered_map<string, int>
公开给 python,结果发现这张地图比 python 的 dict
.
慢 10 倍
见下面的代码。
// map.cpp file
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <string>
#include <unordered_map>
namespace py = pybind11;
PYBIND11_MAKE_OPAQUE(std::unordered_map<std::string, int>);
PYBIND11_MODULE(map, m) {
// map
py::bind_map<std::unordered_map<std::string, int>>(m, "MapStr2Int");
}
在 MacOS 上,使用此命令编译它:
c++ -O3 -std=c++14 -shared -fPIC -Wl,-undefined,dynamic_lookup $(python3 -m pybind11 --includes) map.cpp -o map$(python3-config --extension-suffix)
最后在ipython中与pythondict
比较:
In [20]: import map
In [21]: c_dict = map.MapStr2Int()
In [22]: for i in range(100000):
...: c_dict[str(i)] = i
...:
In [23]: py_dict = {w:i for w,i in c_dict.items()}
In [24]: arr = [str(i) for i in np.random.randint(0,100000, 100)]
In [25]: %timeit [c_dict[w] for w in arr]
59 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [26]: %timeit [py_dict[w] for w in arr]
6.58 µs ± 87.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
正如所见,c_dict
比 python 版本 py_dict
慢得多。
为什么要改进以及如何改进?
您正在比较本机字典实现(来自 Python 标准库的实现)和 pybind 包装的实现。我敢打赌,直接使用 std::unordered_map
的 C++ 程序肯定比用 Python 编写并使用 dict
.
的等效程序快
但这不是你在这里做的。相反,您要求 pybind
生成一个包装器,将 Python 类型转换为 C++ 类型,调用 C++ 标准库 class 方法,然后将结果转换回 Python 类型。这些转换可能需要分配和解除分配,并且确实需要一些时间。此外,pybind
是一个非常聪明(因此很复杂)的工具。您不能指望它生成的代码会像使用 Python API.
直接调用一样优化
除非你打算对哈希函数使用特别优化的算法,否则你将无法编写比标准库更快的 C 或 C++ 代码,因为内置类型已经用 C 语言编码.顶多模仿标准库直接用Python/C API.
应该能和标准库一样快
我将 c++ unordered_map<string, int>
公开给 python,结果发现这张地图比 python 的 dict
.
见下面的代码。
// map.cpp file
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <string>
#include <unordered_map>
namespace py = pybind11;
PYBIND11_MAKE_OPAQUE(std::unordered_map<std::string, int>);
PYBIND11_MODULE(map, m) {
// map
py::bind_map<std::unordered_map<std::string, int>>(m, "MapStr2Int");
}
在 MacOS 上,使用此命令编译它:
c++ -O3 -std=c++14 -shared -fPIC -Wl,-undefined,dynamic_lookup $(python3 -m pybind11 --includes) map.cpp -o map$(python3-config --extension-suffix)
最后在ipython中与pythondict
比较:
In [20]: import map
In [21]: c_dict = map.MapStr2Int()
In [22]: for i in range(100000):
...: c_dict[str(i)] = i
...:
In [23]: py_dict = {w:i for w,i in c_dict.items()}
In [24]: arr = [str(i) for i in np.random.randint(0,100000, 100)]
In [25]: %timeit [c_dict[w] for w in arr]
59 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [26]: %timeit [py_dict[w] for w in arr]
6.58 µs ± 87.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
正如所见,c_dict
比 python 版本 py_dict
慢得多。
为什么要改进以及如何改进?
您正在比较本机字典实现(来自 Python 标准库的实现)和 pybind 包装的实现。我敢打赌,直接使用 std::unordered_map
的 C++ 程序肯定比用 Python 编写并使用 dict
.
但这不是你在这里做的。相反,您要求 pybind
生成一个包装器,将 Python 类型转换为 C++ 类型,调用 C++ 标准库 class 方法,然后将结果转换回 Python 类型。这些转换可能需要分配和解除分配,并且确实需要一些时间。此外,pybind
是一个非常聪明(因此很复杂)的工具。您不能指望它生成的代码会像使用 Python API.
除非你打算对哈希函数使用特别优化的算法,否则你将无法编写比标准库更快的 C 或 C++ 代码,因为内置类型已经用 C 语言编码.顶多模仿标准库直接用Python/C API.
应该能和标准库一样快