std :: array class

时间:2018-01-29 01:00:31

标签: c++ arrays

我曾经做过一些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)时,noLocalityhasLocality更有效,是否存在异常或仅仅为地点发生的数据量不足?

提前致谢

3 个答案:

答案 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 ExcecutionTimerclass,此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 - 64bit8GB Ram上运行此操作。我在启用Intel Quad Core Extreme 3Ghz时使用Visual Studio 2017 CE。我在standard c++17中运行此操作,并进行了所有基本的默认编译器优化。

现在,当我设置debug x64 modeVEC_SIZE = 500时,我没有遇到任何崩溃,但执行时间确实爆炸,我必须等待VEC_SIZE = 1000才能完成执行。< / p>

考虑到这些向量是静态存储的,并且不在堆中。

如果您想要使用堆,可以将5 - 10 minutes用于共享资源,或std::shard_ptr<T>用于拥有的资源。

例如,std::unique_ptr<T>的第一次使用约为VEC_SIZE = 200,但仅从22500ms一直到200,我的控制台输出的时间几乎翻了一倍250&amp;这两个函数43600ms44000ms 2 &amp; 500 2 元素和时间执行爆炸。只是你需要注意的事情。

现在,由于这些向量向量存储1000,其中典型的现代int今天通常为int(不保证 - 依赖于OS / Architecture / Compiler),我们知道{{1每个4 bytes;这将导致该向量的大小约为1000 x 1000 = 10000004 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 pointersc++11,则甚至可以使用c++14的新功能和其他标准容器与新c++17或使用std::vector&#39; std::move()函数而不是std::forward<T>()使用std::vector语义。它将使用emplace_back()调用任何对象构造函数,并且此方法适用于push_back(),但需要注意,因为与众所周知的{{1}相比,如果没有正确完成会导致更多问题这可以说更安全。您可以阅读此堆栈perfect forwardingemplace_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+= operatorbench mark testingcode甚至是您的计算机正在运行的操作系统会有所不同所有目前在ram中都处于活动状态的后台进程。我进行了一些测试,每次对于不同大小的矢量我都会得到相似的结果。我没有注意到两种方法之间在compiler & linker configuration(s)中存在很大的差异,但是当有超过200个 2 元素时,似乎比其他方法慢了几秒。

至于测试你的方法执行时间,我把它们建模在你上面,除了用我的类替换它们。现在,我个人会为这种测试做些什么,我会将debugger configuration(s)移出这些函数,以便函数执行自己的任务,然后在调用这些函数的地方包装一个计时器对象。您的函数定义如下所示:

optimization(s)

对于基准测试,他们会是这样的:

ExecutionTime

这样,您的内部函数定义或实现就不会在其体内嵌入不相关的依赖代码。代码更易于维护,可重用,更清晰,可读性更高。

随意测试上面的代码,玩它来看看有什么不同之处并欢迎ExceutionTimer