热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何提高C++中的std::set_intersection性能?

如何解决《如何提高C++中的std::set_intersection性能?》经验,为你挑选了1个好方法。

在C ++中使用std :: set和Python中使用set()进行实验期间,我遇到了无法解释的性能问题。在C ++中设置交集至少要比Python慢​​3倍。

因此,有人能指出我可以对C ++代码进行的优化和/或解释Python如何更快地做到这一点吗?

我希望他们都可以在set有序的情况下使用O(n)复杂度的相似算法。但是Python可能会做一些优化,以使其系数变小。

set_bench.cc

#include 
#include 
#include 
#include 
#include 
#include 
#include 

void elapsed(std::function f, const std::string& s)
{
    auto start = std::chrono::steady_clock::now();
    f();
    std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
    std::cout <
void fill_set(std::set& s, T start, T end, T step)
{
    for (T i = start; i 
void intersect(const std::set& s1, const std::set& s2, std::set& result)
{
    std::set_intersection(s1.begin(), s1.end(),
                            s2.begin(), s2.end(),
                            std::inserter(result, result.begin()));
}

int main()
{
    std::set s1;
    std::set s2;
    std::set s3;

    elapsed(std::bind(fill_set, std::ref(s1), 8, 1000*1000*100, 13), "fill s1 took");
    elapsed(std::bind(fill_set, std::ref(s2), 0, 1000*1000*100, 7), "fill s2 took");

    std::cout <<"s1 length = " <#!/usr/bin/env python3

import time

def elapsed(f, s):
    start = time.monotonic()
    f()
    elapsed = time.monotonic() - start
    print(f'{s} {elapsed} seconds')

def fill_set(s, start, end, step=1):
    for i in range(start, end, step):
        s.add(i)

def intersect(s1, s2, result):
    result.update(s1 & s2)

s1 = set()
s2 = set()

elapsed(lambda : fill_set(s1, 8, 1000*1000*100, 13), 'fill s1 took')
elapsed(lambda : fill_set(s2, 0, 1000*1000*100, 7), 'fill s2 took')

print(f's1 length = {len(s1)}, s2 length = {len(s2)}')


s3 = set()

elapsed(lambda: intersect(s1, s2, s3), 'intersect s1 and s2 took')

print(f's3 length = {len(s3)}')

# sleep to let check memory consumption
# while True: time.sleep(1)

这是在下一个环境中运行此程序的结果:

铛版本7.0.1

海湾合作委员会8.2.0

的Python 3.7.2

i7-7700 CPU @ 3.60 GHz

$ clang -lstdc++ -O0 set_bench.cc -o set_bench && ./set_bench
fill s1 took 5.38646 seconds
fill s2 took 10.5762 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 1.48387 seconds
s3 length = 1098901
$ clang -lstdc++ -O1 set_bench.cc -o set_bench && ./set_bench
fill s1 took 3.31435 seconds
fill s2 took 6.41415 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 1.01276 seconds
s3 length = 1098901
$ clang -lstdc++ -O2 set_bench.cc -o set_bench && ./set_bench
fill s1 took 1.90269 seconds
fill s2 took 3.85651 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 0.512727 seconds
s3 length = 1098901
$ clang -lstdc++ -O3 set_bench.cc -o set_bench && ./set_bench
fill s1 took 1.92473 seconds
fill s2 took 3.72621 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 0.523683 seconds
s3 length = 1098901
$ gcc -lstdc++ -O3 set_bench.cc -o set_bench && time ./set_bench
fill s1 took 1.72481 seconds
fill s2 took 3.3846 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 0.516702 seconds
s3 length = 1098901
$ python3.7 ./set_bench.py 
fill s1 took 0.9404696229612455 seconds
fill s2 took 1.082577683031559 seconds
s1 length = 7692308, s2 length = 14285715
intersect s1 and s2 took 0.17995300807524472 seconds
s3 length = 1098901

如您所见,结果是相等的,因此我假设两个程序都执行相同的计算。

顺便说一句-C ++程序的RSS是1084896 kB,Python的RSS是1590400 kB。



1> rustyx..:

这篇文章有两个问题:

问:如何提高std::set_intersectionC ++的性能?

使用sorted std::vector而不是set,这对缓存更友好。由于相交是在单遍中按顺序完成的,因此它将尽可能快。在我的系统上,我运行时间为0.04 s。如果这是您需要的,请在这里停止。

问:... Python如何如此快地完成呢?

换句话说,“ 为什么Python的设置比C ++的设置快? ”。在余下的文章中,我将重点讨论这个问题。

首先,Python set是一个哈希表,并且std::set是一个二叉树。因此,用于std::unordered_set将苹果与苹果进行比较(基于O(logN)查找复杂度,我们此时拒绝二叉树)。

还要注意,这std::set_intersection只是一个两指针算法;它遍历两个排序的集合,仅保留匹配的值。除了它的名称之外,它与Python的并没有什么共同之处set_intersection,它本身只是一个简单的循环:

遍历较小的哈希表

对于每个元素,如果它存在于另一个哈希表中,则将其添加到结果中

因此,我们不能std::set_intersection在未排序的数据上使用,而需要实现循环:

    for (auto& v : set1) {
        if (set2.find(v) != set2.end()) {
            result.insert(v);
        }
    }

这里没什么好看的。不幸的是,虽然这种算法上的直接应用std::unordered_set仍然较慢通过的3倍。怎么可能呢?

    我们观察到输入数据集的大小> 100MB。这无法容纳i7-7700的8MB缓存,这意味着您可以在8MB的边界内进行的工作越多,程序执行的速度就越快。

    Python使用类似于PHP哈希表(通常是开放式寻址哈希表的类)的特殊形式的“密集哈希表”,而C ++ 通常是幼稚的或列表向量的哈希表。密集结构对缓存更友好,因此速度更快。有关实现的详细信息,请参见dictobject.c和setobject.c。std::unordered_set

    对于std::hash要生成的已经独特的输入数据集,内置的C ++ 太复杂了。另一方面,Python使用标识(无操作)哈希函数来存储最大为2 30的整数(请参阅参考资料long_hash)。冲突由其哈希表实现中内置的LCG摊销。您无法将其与C ++标准库功能相匹配;不幸的是,此处的身份哈希将再次导致哈希表太稀疏。

    Python使用自定义内存分配器pymalloc,它类似于jemalloc并针对数据局部性进行了优化。它通常比内置Linux tcmalloc更好,后者是C ++程序通常使用的。

有了这些知识,我们可以设计出性能类似的C ++版本,以证明技术可行性:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std::chrono_literals;

void elapsed(std::function f, const std::string& s)
{
    auto start = std::chrono::steady_clock::now();
    f();
    auto end = std::chrono::steady_clock::now();
    std::cout <
struct myhash {
    size_t operator()(T x) const {
        return x / 5; // cheating to improve data locality
    }
};

template 
using myset = std::unordered_set>;

template 
void fill_set(myset& s, T start, T end, T step)
{
    s.reserve((end - start) / step + 1);
    for (T i = start; i 
void intersect(const myset& s1, const myset& s2, myset& result)
{
    result.reserve(s1.size() / 4); // cheating to compete with a better memory allocator
    for (auto& v : s1)
    {
        if (s2.find(v) != s2.end())
            result.insert(v);
    }
}

int main()
{
    myset s1;
    myset s2;
    myset s3;

    elapsed(std::bind(fill_set, std::ref(s1), 8, 1000 * 1000 * 100, 13), "fill s1 took");
    elapsed(std::bind(fill_set, std::ref(s2), 0, 1000 * 1000 * 100, 7), "fill s2 took");

    std::cout <<"s1 length = " <fill s1 took 0.321397 seconds
fill s2 took 0.529518 seconds
s1 length = 7692308, s2 length = 14285714
intersect s1 and s2 took 0.0974416 seconds
s3 length = 1098901

还是比Python快2.8倍,同时保留了哈希集功能!


PS One会想-为什么C ++标准库实现如此慢的哈希表?非自由午餐定理也适用于此:基于探测的解决方案并不总是那么快。作为一种机会主义的解决方案,它有时会遭受“团块”(不断探查占用的空间)的困扰。当这种情况发生时,性能将成倍下降。标准库实现的思想是保证所有可能的输入具有可预测的性能。不幸的是,正如对钱德勒·卡鲁斯(Chandler Carruth)在演讲中解释的那样,尽管对现代硬件的缓存效果实在太大而无法忽略。


推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • C++中的三角函数计算及其应用
    本文介绍了C++中的三角函数的计算方法和应用,包括计算余弦、正弦、正切值以及反三角函数求对应的弧度制角度的示例代码。代码中使用了C++的数学库和命名空间,通过赋值和输出语句实现了三角函数的计算和结果显示。通过学习本文,读者可以了解到C++中三角函数的基本用法和应用场景。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
author-avatar
j酱油
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有