我正在与一些朋友讨论一段代码,我们讨论了在C中使用memset函数,如果我们初始化一个大小为N的数组,这是该函数的Big-O表示法的顺序?
答案 0 :(得分:13)
在您可以直接访问页表并且以分层方式存储的系统上,memset
可以通过使用copy-on替换整个虚拟地址映射在O(log n)
中实现-write对充满给定字节值的单个页面的引用。但请注意,如果您将来对该对象进行任何修改,O(n)
的正常memset
成本将被推迟到页面错误,以便在它们出现时实例化单独的页面副本修改。
答案 1 :(得分:12)
你问过复杂性,但你可能想问一下性能。
复杂性,用符号O(n)表示,是一个概念,涉及算法中的操作数量如何随着问题规模的增长而增长。 O(n)表示必须执行与输入大小成比例的一些步骤。它没有说明这个比例是多少。 memset是O(n)。 O(n 2 )表示必须执行与n 2 成比例的一些步骤。 memset不是O(n 2 ),因为设置2n个字节所需的工作量只是n个字节的两倍,而不是工作量的四倍。
你可能对memset的性能更感兴趣,因为memset的库版本比你可能编写的C版本执行得快得多。
库版本的执行速度要快得多,因为它使用专门的指令。最常见的现代处理器具有允许它们在一条指令中将16字节写入存储器的指令。库实现者使用汇编语言或与其接近的东西编写memset等关键函数,因此他们可以访问所有这些指令。
使用C编写时,编译器很难利用这些指令。例如,指向您正在设置的内存的指针可能不会与16个字节的倍数对齐。 memset作者将编写测试指针的代码,并为每种情况分支到不同的代码,目标是单独设置一些字节,然后使用一个对齐的指针,这样他们就可以使用存储16字节的快速指令。时间。这只是库编写器在编写memset等例程时遇到的许多复杂问题之一。
由于这些复杂性,编译器无法轻松采用memset的C实现并将其转换为专家编写的快速代码。当编译器在C代码中看到一次写入一个字节的循环时,它通常会生成一次写入一个字节的汇编语言。优化器变得更加智能,但复杂性限制了他们可以做多少以及他们可以做多少而无需生成大量代码来处理可能很少发生的情况。
答案 2 :(得分:1)
复杂性为O(n)。这是基本的东西。
答案 3 :(得分:1)
某些C库提供memset()
的矢量化版本。除非您的编译器执行自动矢量化和循环展开,否则for
循环将比矢量化memset()
慢。是否进行了矢量化,memset()
受内存带宽的限制,最小时间与数组大小除以内存带宽成正比,即当存储器带宽恒定时,它是O(n)操作。
在NUMA机器上,可以对非常大的阵列进行线程化,以实现NUMA节点数量级的加速。有关基准测试,请参阅this answer。