我曾经做过一些C#和Java,但最近我想学习c ++以及我发现许多大公司更喜欢c ++来提高效率。
为了习惯C ++的语法和行为,我开始将我在Java中完成的测试转换为C ++代码,以下是其中之一:
#include "stdafx.h"
#include <iostream>
#include <array>
#include <string>
#include <cstring>
#include <chrono>
using namespace std;
using namespace std::chrono;
typedef array<array<int, 1000>, 1000> aArray;
aArray hasLocality(aArray, aArray);
aArray noLocality(aArray, aArray);
static aArray a = aArray();
static aArray b = aArray();
int main() {
for (size_t i = 0; i < 100; i++)
{
for (size_t j = 0; j < 100; j++)
{
a[i][j] = i + j;
b[i][j] = i + j;
}
}
hasLocality(a, b);
noLocality(a, b);
system("pause");
return 0;
}
aArray hasLocality(aArray a, aArray b) {
milliseconds startTime = duration_cast<milliseconds>(
system_clock::now().time_since_epoch()
);
aArray ans = aArray();
for (size_t i = 0; i < ans.size(); i++)
{
for (size_t k = 0; k < ans[0].size(); k++)
{
for (size_t j = 0; j < ans[0].size(); j++)
{
ans[i][j] = ans[i][j] + a[i][k] * b[k][j];
}
}
}
milliseconds endTime = duration_cast<milliseconds>(
system_clock::now().time_since_epoch()
);
string time = std::to_string((endTime - startTime).count()) + "\n";
cout.write(time.c_str(), (unsigned)strlen(time.c_str()));
return ans;
}
aArray noLocality(aArray a, aArray b) {
milliseconds startTime = duration_cast<milliseconds>(
system_clock::now().time_since_epoch()
);
aArray ans = aArray();
for (size_t i = 0; i < ans.size(); i++)
{
for (size_t j = 0; j < ans[0].size(); j++)
{
for (size_t k = 0; k < ans[0].size(); k++)
{
ans[i][j] = ans[i][j] + a[i][k] * b[k][j];
}
}
}
milliseconds endTime = duration_cast<milliseconds>(
system_clock::now().time_since_epoch()
);
string time = std::to_string((endTime - startTime).count()) + "\n";
cout.write(time.c_str(), (unsigned)strlen(time.c_str()));
return ans;
}
这是我通过简单矩阵乘法测试局部性的一种方法,但由于数据量过大而无法摆脱堆栈溢出异常,我认为这对测试至关重要。
我还认为数组会放在堆而不是堆栈上,因为我把它放在静态。
最后,我发现当给定较小的数组大小(100,100)时,noLocality
比hasLocality
更有效,是否存在异常或仅仅为地点发生的数据量不足?
提前致谢
答案 0 :(得分:4)
堆栈大小有限(在桌面系统上通常约为1到几兆字节)。大对象(例如array<array<int, 1000>, 1000>
的实例)很容易超出此限制。动态或静态地分配大对象以避免溢出。
我还认为数组会放在堆而不是堆栈上,因为我把它放在静态。
ans
不是静态的。它将放在堆栈上。
最后,我发现当给定较小的数组大小(100,100)时,noLocality比hasLocality更有效,那个地方的数据是否异常或者数量不足?
数组很可能太小而无法显示缓存局部性的影响,因为100 * 100 int数组足够小以完全适合L1缓存(假设Pentium Dual-Core或更高版本)。因此,它的阅读顺序没有区别。
答案 1 :(得分:4)
在Java中,所有对象参数都通过引用传递。在C ++中,默认值是它们按值传递(即复制放在堆栈上)。所以当你打电话时:
aArray hasLocality(aArray a, aArray b)
最终得到堆栈上的a副本,后跟b的副本,并且还为返回值分配了空间,aArray的另一个副本。您在堆栈上分配了3个大型数组。这与Java不同。
在C ++中,您可以通过使用引用或指针来避免传递值。 Java没有指针。 Java引用与C ++引用并不完全相同,但有相似之处。
所以如果你有:
aArray hasLocality(aArray &a, aArray &b)
然后你得到类似于Java的东西,数组通过引用传递,与Java相同。 对hasLocality的调用也不例外。所以只需用这种方式改变hasLocality和noLocality。 您仍然拥有返回值副本。为了避免这种情况,你可以做的一件事就是传递返回值:
void hasLocality(aArray &a, aArray &b, aArray &ans)
然后移动
aArray ans = aArray();
在功能之外。
此时你没有像Java一样进行数组复制。但请记住,C ++中的引用有点不同,一旦引用引用了一个对象,它就不能引用任何其他对象。由于你是C ++的新手,这可能会使你现在感到困惑,但你会学到的。请注意,C ++比Java整体更复杂。
答案 2 :(得分:1)
<小时/> 在阅读了您的问题并查看了您对c ++代码实现的尝试之后;我已经重新编写了您的代码,原因有几个,这些原因将在本答案中列出。在我向您展示我所做的事情之前,让我们从您提到的内容开始,然后我们将从那里开始:
我还认为数组会放在堆而不是堆栈上,因为我把它放在静态。
在c++
static
存储空间与heap
不同。要使用堆,您必须使用关键字new
&amp; delete
或其数组版本new[]
&amp; delete[]
然而modern c++
分别对他们不满意,除非他们是绝对必要的&amp;你确切知道自己在做什么,以及如何正确使用它们,否则使用std::shared_ptr<T>
或std::unique_ptr<T>
让你的生活和你的代码的用户生活更为明智。更好,因为这些被称为smart pointers
。他们管理分配和尽管它们不是100%
防弹,但它们以干净和安全的方式取消分配堆,它们相当容易且易于使用,并且优于其他内存管理方法。还有另一种方法可以访问C++
中的堆,这是通过C standard library
,因为这些函数是malloc()
&amp;来自标题free()
的{{1}},但他们也被建议反对并且也有充分的理由。请在此处查看此答案:stack: The c++ Memory Model。
我通过以下方式对您的代码进行了一些更改:
- 添加一个简单的类来执行
<cstdlib>
,因为它可以让生活更轻松。
time execution profiling
ExcecutionTimer
是class
,此header only class
通过删除重复的代码使您的其余代码更易于阅读。- 从
class
中删除using "namespace"
。
- 将来会为您节省麻烦。
- 我更改了您的
global scope
function(s)'
。
- 适当的容器,更易于使用,更高效,可维护性,可重用性和可读性。
- 最后我使用
declarations-definitions
代替std::vector<T>
- 出于上述类似原因
好的,现在我的修改后的代码:
<强> ExecutionTimer.h 强>
std::array<size, T>
<强>的main.cpp 强>
#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H
#include <chrono>
#include <type_traits>
#include <sstream>
#include <iostream>
template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock>;
private:
const Clock::time_point mStart = Clock::now();
public:
ExecutionTimer() = default;
~ExecutionTimer() {
const auto end = Clock::now();
std::ostringstream strStream;
strStream << "Destructor Elapsed: "
<< std::chrono::duration_cast<Resolution>(end - mStart ).count()
<< std::endl;
std::cout << strStream.str() << std::endl;
}
inline void stop() {
const auto end = Clock::now();
std::ostringstream strStream;
strStream << "Stop Elapsed: "
<< std::chrono::duration_cast<Resolution>(end - mStart).count()
<< std::endl;
std::cout << strStream.str() << std::endl;
}
};
#endif // !EXECUTION_TIMER_H
控制台输出
设置
时可能的输出#include <vector> #include <iostream> #include "ExecutionTimer.h" // Don't Like Magic Numbers So I Set A Constant Unsigned For The Vectors Size const unsigned VEC_SIZE = 200; // Using std::vector<T> instead of std::array<val, T> // However 2 typedefs are needed to set both sizes. typedef std::vector<int> Ints; typedef std::vector<Ints> VecInts; // Passing by const reference for both input vectors. // Moved the return to a reference param and defaulted it. // Changed the functions return types to void. // Finally, I added a defaulted size parameter. void hasLocality( const VecInts& A, const VecInts& B, VecInts& Ans = VecInts(VEC_SIZE, Ints(VEC_SIZE)), const unsigned size = VEC_SIZE ); void noLocality( const VecInts& B, const VecInts& B, VecInts& Ans = VecInts(VEC_SIZE, Ints(VEC_SIZE)), const unsigned size = VEC_SIZE ); int main() { try { // Create vectors a & b that have 1000 vectors of 1000 ints. static VecInts A( VEC_SIZE, Ints( VEC_SIZE ) ); static VecInts B( VEC_SIZE, Ints( VEC_SIZE ) ); // If you need the values returned by the functions make local copy // here as they will be passed by reference. VecInts AnsHasLoc( VEC_SIZE, Ints( VEC_SIZE ) ); VecInts AnsNoLoc( VEC_SIZE, Ints( VEC_SIZE ) ); // changed `std::size_t to just unsigned as you are only doing array indexing on these vectors for ( unsigned i = 0; i < 100; i++ ) { for ( unsigned j = 0; j < 100; j++ ) { A[i][j] = i + j; B[i][j] = i + j; } } // Last 2 parameters are defaulted and omitted. // The second to last is a return value by reference. // The third is the internal size of the vectors hasLocality( A, B ); noLocality( A, B ); // Same as above but passing in the return values by reference, // still leaving the size defaulted. hasLocality( A, B AnsHasLoc ); noLocality( A, B, AnsNoLoc ); } catch ( std::exception e ) { std::cout << e.what() << std::endl; std::cout << "\nPress any key and enter to quit." << std::endl; char q; std::cin >> q; return -1; } std::cout << "\nPress any key and enter to quit." << std::endl; char q; std::cin >> q; return 0; } void hasLocality( const VecInts& A, const VecInts& B, VecInts& Ans, const unsigned size ) { ExecutionTimer<> timer; // default template param = milliseconds // No need to declare local stack temp copy for return value. // Return it by reference from parameter list. // VecInts Ans( size, Ints( size ) ); for ( unsigned i = 0; i < Ans.size(); i++ ) { for ( unsigned k = 0; k < Ans[0].size(); k++ ) { for ( unsigned j = 0; j < Ans[0].size(); j++ ) { Ans[i][j] = Ans[i][j] + A[i][k] * B[k][j]; } } } timer.stop(); // return Ans; // No need to return local stack copy. } void noLocality( const VecInts& A, const VecInts& B, VecInt& Ans, const unsigned size ) { ExecutionTimer<> timer; // default in milliseconds // No need to declare local stack temp copy for return value; // Return it by reference from parameter list. // VecInts Ans( size, Ints( size ) ); for ( unsigned i = 0; i < Ans.size(); i++ ) { for ( unsigned j = 0; j < Ans[0].size(); j++ ) { for ( unsigned k = 0; k < Ans[0].size(); k++ ) { Ans[i][j] = Ans[i][j] + A[i][k] * B[k][j]; } } } timer.stop(); // return Ans; // No need to return local stack copy }
VEC_SIZE = 200
设置
时可能的输出Stop Elapsed: 22733 Destructor Elapsed: 22734 Stop Elapsed: 22499 Destructor Elapsed: 22500 Press any key and enter to quit.
VEC_SIZE = 100
注意: - 我在
Stop Elapsed: 2909 Destructor Elapsed: 2910 Stop Elapsed: 2815 Destructor Elapsed: 2816 Press any key and enter to quit.
上使用Windows 7 Home Premium - 64bit
在8GB Ram
上运行此操作。我在启用Intel Quad Core Extreme 3Ghz
时使用Visual Studio 2017 CE
。我在standard c++17
中运行此操作,并进行了所有基本的默认编译器优化。
现在,当我设置debug x64 mode
或VEC_SIZE = 500
时,我没有遇到任何崩溃,但执行时间确实爆炸,我必须等待VEC_SIZE = 1000
才能完成执行。< / p>
考虑到这些向量是静态存储的,并且不在堆中。
如果您想要使用堆,可以将5 - 10 minutes
用于共享资源,或std::shard_ptr<T>
用于拥有的资源。
例如,std::unique_ptr<T>
的第一次使用约为VEC_SIZE = 200
,但仅从22500ms
一直到200
,我的控制台输出的时间几乎翻了一倍250
&amp;这两个函数43600ms
。 44000ms
2 &amp; 500
2 元素和时间执行爆炸。只是你需要注意的事情。
现在,由于这些向量向量存储1000
,其中典型的现代int
今天通常为int
(不保证 - 依赖于OS / Architecture / Compiler),我们知道{{1每个4 bytes
;这将导致该向量的大小约为1000 x 1000 = 1000000
或4 Bytes
。所以这接近于超出堆栈和/或页面文件的限制。任何比这更大的东西我都会高度推荐:
- 在合适的地方使用堆。
- 将此容器拆分为较小的缓冲区或分区
- 做一批&amp; queue-dequeue类型处理
- 使用多线程和/或并行编程。
- 模板元编程
- 或上述任意组合。
除了使用智能指针之外,使用堆的示例基本上与上面的完全相同。
4 million bytes
如果使用4MB
#include <vector>
#include <memory>
#include <iostream>
const unsigned VEC_SIZE = 250;
typedef std::vector<int> Ints;
typedef std::vector<Ints> VecInts;
// functions are the same & have not changed.
int main() {
try {
// Uncomment the key word to make these shared pointers static.
/*static*/ std::shared_ptr<VecInts> A =
std::make_shared<VecInts>( VecInts( VEC_SIZE, Ints( VEC_SIZE ) ) );
/*static*/ std::shared_ptr<VecInts> B =
std::make_shared<VecInts>( VecInts( VEC_SIZE, Ints( VEC_SIZE ) ) );
// The shared pointers above are only of the outer container itself
// Another possible way would be to make the internal vectors hold `shared_ptrs`
// this would involve having to change the typedefs above.
// If needing the values returned by functions create local copy here
// as they will be passed by reference
VecInts AnsHasLoc( VEC_SIZE, Ints( VEC_SIZE ) );
VecInts AnsNoLoc( VEC_SIZE, Ints( VEC_SIZE ) );
// Now To Use The Smart Pointers
for ( unsigned i = 0; i < VEC_SIZE; i++ ) {
for ( unsigned j = 0; j < VEC_SIZE; j++ ) {
// Need To Dereference Them
(*A)[i][j] = i + j;
(*B)[i][j] = i + j;
}
}
// Need To Dereference Them & Using the local copies passed by reference
hasLocality( *A, *B, AnsHasLoc );
noLocality( *A, *B, AnsNoLoc );
} catch ( ... ) {
// message
}
// message
return 0;
}
而不是vector
shared_ptr
的{{1}},则shared_ptr
可能如下所示:
vector
一旦你习惯使用typedef
,设置值的代码并不是那么困难。如果您可以使用typedef std::vector<std::shared_ptr<int>> IntsPtr;
typedef std::vector<std::shared_ptr<Ints>> VecIntsPtr;
,smart pointers
或c++11
,则甚至可以使用c++14
的新功能和其他标准容器与新c++17
或使用std::vector
&#39; std::move()
函数而不是std::forward<T>()
使用std::vector
语义。它将使用emplace_back()
调用任何对象构造函数,并且此方法适用于push_back()
,但需要注意,因为与众所周知的{{1}相比,如果没有正确完成会导致更多问题这可以说更安全。您可以阅读此堆栈perfect forwarding
:emplace_back() and perfect forwarding。
现在当我在智能指针被声明为本地(堆栈)或静态存储的情况下使用智能指针运行上面的代码时...我确实得到了与向量的内部大小相同的结果smart pointers
x push_back()
的大小约为Q/A
&amp;对于250
和&amp;的两个函数,250
43753ms
&amp; 44604ms
static shared_ptr<>
。
可以在triple for循环内的两个函数中进行小的优化。您可以分别在triple for循环中更改这两行:
43936ms
当我这样做时:44008ms
local-stack shared_ptr<>
报告了// Ans[i][j] = Ans[i][j] + A[i][k] * B[k][j]; // To
Ans[i][j] += A[i][k] * B[k][j];
&amp;具有非静态VEC_SIZE = 250
版本的ExecutionTimer
。只需使用34679ms
而不是将其展开,它就会耗费大约34979ms
的时间。
至于你的另一个问题:
最后,我发现当给定较小的数组大小(100,100)时,noLocality比hasLocality更有效,那个地方的数据是否异常或者数量不足?
执行shared_ptr<>
时,10000ms
,+= operator
,bench mark testing
,code
甚至是您的计算机正在运行的操作系统会有所不同所有目前在ram中都处于活动状态的后台进程。我进行了一些测试,每次对于不同大小的矢量我都会得到相似的结果。我没有注意到两种方法之间在compiler & linker configuration(s)
中存在很大的差异,但是当有超过200个 2 元素时,似乎比其他方法慢了几秒。
至于测试你的方法执行时间,我把它们建模在你上面,除了用我的类替换它们。现在,我个人会为这种测试做些什么,我会将debugger configuration(s)
移出这些函数,以便函数执行自己的任务,然后在调用这些函数的地方包装一个计时器对象。您的函数定义如下所示:
optimization(s)
对于基准测试,他们会是这样的:
ExecutionTime
这样,您的内部函数定义或实现就不会在其体内嵌入不相关的依赖代码。代码更易于维护,可重用,更清晰,可读性更高。
随意测试上面的代码,玩它来看看有什么不同之处并欢迎ExceutionTimer
!