低于20亿的质量 - 使用STD ::列表阻碍性能
prime number below 2 billion - usage of std::list hinders performance
问题语句是在时间范围内找到低于20亿低于20亿的质量。20秒我遵循以下方法。
-
将数字n与数字>列表k(k< sqrt(n)) - 花了20秒
-
将数字n划分为 SQRT(n)下方的质量列表>。
有人可以帮助我理解为什么第二种方法要长期花费很长时间,即使我们没有将分区减少50%(大约)?还是我选择了错误的数据结构?
方法1:
#include <iostream>
#include<list>
#include <ctime>
using namespace std;
list<long long> primeno;
void ListPrimeNumber();
int main()
{
clock_t time_req = clock();
ListPrimeNumber();
time_req = clock() - time_req;
cout << "time taken " << static_cast<float>(time_req) / CLOCKS_PER_SEC << " seconds" << endl;
return 0;
}
void check_prime(int i);
void ListPrimeNumber()
{
primeno.push_back(2);
primeno.push_back(3);
primeno.push_back(5);
for (long long i = 6; i <= 20000000; i++)
{
check_prime(i);
}
}
void check_prime(int i)
{
try
{
int j = 0;
int limit = sqrt(i);
for (j = 2 ; j <= limit;j++)
{
if(i % j == 0)
{
break;
}
}
if( j > limit)
{
primeno.push_back(i);
}
}
catch (exception ex)
{
std::cout << "Message";
}
}
方法2:
#include <iostream>
#include<list>
#include <ctime>
using namespace std;
list<long long> primeno;
int noofdiv = 0;
void ListPrimeNumber();
int main()
{
clock_t time_req = clock();
ListPrimeNumber();
time_req = clock() - time_req;
cout << "time taken " << static_cast<float>(time_req) / CLOCKS_PER_SEC << " seconds" << endl;
cout << "No of divisions : " << noofdiv;
return 0;
}
void check_prime(int i);
void ListPrimeNumber()
{
primeno.push_back(2);
primeno.push_back(3);
primeno.push_back(5);
for (long long i = 6; i <= 10000; i++)
{
check_prime(i);
}
}
void check_prime(int i)
{
try
{
int limit = sqrt(i);
for (int iter : primeno)
{
noofdiv++;
if (iter <= limit && (i%iter) == 0)
{
break;
}
else if (iter > limit)
{
primeno.push_back(i);
break;
}
}
}
catch (exception ex)
{
std::cout << "Message";
}
}
您的第二个示例需要更长的原因是您要迭代std::list
。
C 中的std::list
是一个链接列表,这意味着它不使用连续的内存。这很糟糕,因为要迭代列表,您必须以无法预测的方式(到CPU/Prefetcher)以(到CPU/Prefetcher)的方式跳到节点。另外,您很可能只"使用"每个Cacheline的几个字节。公羊很慢。从RAM获取字节比从L1获取 lot 更长。这些天CPU很快,因此您的程序大部分时间都不做任何事情并等待内存到达。
改用std::vector
。它一个又一个一个接一个地存储所有值,并且迭代非常便宜。由于您在内存中迭代而无需跳跃,因此您使用的是 full cacheline,您的预摘要将能够在需要之前获取更多页面,因为您可以预测内存的访问。
包括Bjarne Stroustrup在内的许多人证明,std::vector
在很多情况下都比std::list
快,即使在std::list
在理论上具有"更好的复杂性"(随机插入,删除,...)因为缓存很有帮助。因此,始终将std::vector
用作默认值。如果您思考在您的情况下,链接列表将更快, MEATUR IT,并且很惊讶 - 大多数时间 - std::vector
占主导地位。
编辑:正如其他人所指出的那样,您查找素数的方法不是很有效。我只是玩了一下,并使用bitset实现了eRatosthenes的筛子。
constexpr int max_prime = 1000000000;
std::bitset<max_prime> *bitset = new std::bitset<max_prime>{};
// Note: Bit SET means NO prime
bitset->set(0);
bitset->set(1);
for(int i = 4; i < max_prime ; i += 2)
bitset->set(i); // set all even numbers
int max = sqrt(max_prime);
for(int i = 3; i < max; i += 2) { // No point testing even numbers as they can't be prime
if(!bitset->test(i)) { // If i is prime
for(int j = i * 2; j < no_primes; j += i)
bitset->set(j); // set all multiples of i to non-prime
}
}
这需要 4.2和4.5秒之间我的机器上的十亿(1,000,000,000)。您的方法甚至花了1亿美元。我大约两分钟后取消了10亿次搜索。
比较1亿:
time taken: 63.515 seconds
time taken bitset: 1.874 seconds
No of divisions : 1975961174
No of primes found: 5761455
No of primes found bitset: 5761455
我不是数学家,所以我很确定仍然有进一步优化的方法,我只对数字进行优化。
首先要做的是确保您正在启用优化。C 标准库模板类别在生成大量函数调用时,使用不优化的代码往往会表现较差。优化器插入了大多数这些功能调用,使它们便宜得多。
std::list
是链接列表。在您要随机插入或删除元素的情况下,它最有用(即不是从末端)。
对于仅附加到列表末尾的情况, std::list
有以下问题:
- 通过列表进行迭代相对昂贵,因为代码必须遵循节点指针,然后检索数据
- 列表使用了更多的内存,除了实际数据外,每个元素还需要指向上一个和下一个节点的指针。在64位系统上,对于
int
的列表
,它等于每个元素的20个字节,而不是4个字节。 - 由于列表中的元素在内存中不连续,因此编译器无法执行那么多的SIMD优化,您将遭受CPU缓存的更多障碍
std::vector
将解决上述所有内容,因为它的内存是连续的,并且通过它迭代基本上只是一种增加数组索引的情况。您确实需要确保在开始时以足够大的值在向量上调用reserve
,以便将矢量附加到矢量不会导致整个数组复制到新的较大较大数组中。
比上述更大的优化是使用eratosthenes的筛子来计算您的素数。由于生成此灯需要随机删除(取决于您的确切实现)std::list
的性能可能比std::vector
更好,尽管即使在这种情况下,std::list
的开销也可能不会超过其成本。
iDeOne (几乎没有表面更改的OP代码)的测试完全矛盾在这个问题中:
/* check_prime__list:
time taken No of divisions No of primes
10M: 0.873 seconds 286144936 664579
20M: 2.169 seconds 721544444 1270607 */
2B: projected time: at least 16 minutes but likely much more (*)
/* check_prime__nums:
time taken No of divisions No of primes
10M: 4.650 seconds 1746210131 664579
20M: 12.585 seconds 4677014576 1270607 */
2B: projected time: at least 3 hours but likely much more (*)
我还将分区数量计数器数的类型更改为long int
,因为它围绕数据类型限制了。因此,他们可能一直在误解。
但是运行时间没有受到影响。壁时钟是壁钟。
最有可能的解释似乎是OP的草率测试,并且在每个测试案例中使用了不同的值。
(*)时间投影是通过生长分析的经验顺序进行的:
100**1.32 * 2.169 / 60 = 15.8
100**1.45 * 12.585 / 3600 = 2.8
在给定尺寸范围内测量的经验增长顺序对于列表算法, n 1.32 vs.> 1.45 用于所有数字测试。从理论上的复杂性中,这完全可以预期,因为在 log n 上,所有数字的数量少于所有数字,而总复杂性为 o。(n 1.5 /log n) vs. o(n 1.5 )。击败实际算法优势的任何实施差异也极不可能。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- Pybind11:将元组列表从Python传递到C++
- 从链接列表c++中删除一个项目
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- C++如何通过用户输入删除列表元素
- 读取文件的最后一行并输入到链接列表时出错
- OpenMP阵列性能较差
- 复制列表初始化的隐式转换的等级是多少
- 列表和forward_list性能之间的区别?
- unordered_set与链接列表之间的性能比较
- 低于20亿的质量 - 使用STD ::列表阻碍性能
- C 初始化列表std ::数组最佳性能
- 在C++中使用指针列表(继承还是性能?
- 排序列表的最佳数据结构(性能)
- 列表排序和结构体向量排序之间的性能差距.c++
- c++中初始化列表的性能
- STL列表的性能非常差
- 在并行追加时,使用向量列表和向量的向量对性能的影响
- vs2015 上的 stl 列表性能不佳,同时删除包含迭代器到自身在列表中的位置的节点
- C++/Haskell中的精确实数运算和惰性列表性能