我有一个vector<Mat> my_vect
,每个垫子都是浮动的,它们的大小是90 * 90。我开始从磁盘加载矩阵,我将16000矩阵加载到该向量。在我完成这些矩阵的工作后,我清除了它们。这是我加载和清除向量的代码:
Mat mat1(90,90,CV_32F);
load_vector_of_matrices("filename",my_vect); //this loads 16K elements
//do something
for(i = 1:16K)
correlate(mat1, my_vect.at(i));
my_vect.clear();
为了提高效率,我正在加载16K元素。
现在我的问题是阅读所有这些矩阵需要3-4秒,而my_vect.clear()
花费大约相同的时间量。
根据这个answer,我需要花费O(n)
时间vector<Mat>
没有一个简单的析构函数。
为什么清算花费这么多时间,矩阵析构函数会覆盖矩阵中的每个索引? 有没有办法减少清除矢量的时间?
修改 我正在使用Visual Studio 2010,优化级别是最大化速度(/ O2)。
答案 0 :(得分:2)
首先,一个流加载器。为它提供一个函数,给定一个max,返回一个数据向量(又名loader<T>
)。它可以存储内部状态,但会被复制,因此将该内部状态存储在std::shared_ptr
中。我保证只会调用它的一个副本。
您不负责从您的加载程序返回所有max
数据,但如您所写必须至少返回1个元素。返回更多是肉汁,并可能减少线程开销。
然后拨打streaming_loader<T>( your_loader, count )
。
返回std::shared_ptr< std::vector< std::future< T > > >
。您可以等待这些期货,但必须按顺序等待它们(第二个不保证在第一个提供数据之前等待,直到第一个提供数据)。
template<class T>
using loader = std::function< std::vector<T>(size_t max) >;
template<class T>
using stream_data = std::shared_ptr< std::vector< std::future<T> > >;
namespace details {
template<class T>
T streaming_load_some( loader<T> l, size_t start, stream_data<T> data ) {
auto loaded = l(data->size()-start);
// populate the stuff after start first, so they are ready:
for( size_t i = 1; i < loaded.size(); ++i ) {
std::promise<T> promise;
promise.set_value( std::move(loaded[i]) );
(*data)[start+i] = promise.get_future();
}
if (start+loaded.size() < data->size()) {
// recurse:
std::size_t new_start = start+loaded.size();
(*data)[new_start] = std::async(
std::launch::async,
[l, new_start, data]{return streaming_load_some<T>( l, new_start, data );}
);
}
// populate the future:
return std::move(loaded.front());
}
}
template<class T>
stream_data< T >
streaming_loader( loader<T> l, size_t n ) {
auto retval = std::make_shared<std::vector< std::future<T> >>(n);
if (retval->empty()) return retval;
retval->front() = std::async(
std::launch::async,
[retval, l]()->T{return details::streaming_load_some<T>( l, 0, retval );
});
return retval;
}
使用时,您可以使用stream_data<T>
(也称为未来数据向量的共享指针),迭代它,然后依次.get()
。然后进行处理。如果你需要一个50块,请依次打电话给.get()
,直到你达到50 - 不跳到50号。
这是一个完全玩具装载机和测试工具:
struct loader_state {
int index = 0;
};
struct test_loader {
std::shared_ptr<loader_state> state; // current loading state stored here
std::vector<int> operator()( std::size_t max ) const {
std::size_t amt = max/2+1;// really, really stupid way to decide how much to load
std::vector<int> retval;
retval.reserve(amt);
for (size_t i = 0; i < amt; ++i) {
retval.push_back( -(int)(state->index + i) ); // populate the return value
}
state->index += amt;
return retval;
}
// in real code, make this constructor do something:
test_loader():state(std::make_shared<loader_state>()) {}
};
int main() {
auto data = streaming_loader<int>( test_loader{}, 1024 );
std::size_t count = 0;
for( std::future<int>& x : *data ) {
++count;
int value = x.get(); // get data
// process. In this case, print out 100 in blocks of 10:
if (count * 100 / data->size() > (count-1) * 100 / data->size())
std::cout << value << ", ";
if (count * 10 / data->size() > (count-1) * 10 / data->size())
std::cout << "\n";
}
std::cout << std::endl;
// your code goes here
return 0;
}
count
可能会也可能不会毫无价值。上面的加载器的内部状态非常值得,我只是用它来演示如何存储一些状态。
你可以做类似的事情来破坏一堆物体,而无需等待它们的析构函数完成。或者,您可以依赖这样的事实:在您处理数据并等待下一个数据加载时,可能会破坏您的数据。
在工业强度解决方案中,除了其他方面,您还需要包含中止所有这些内容的方法。例外可能是一种方式。此外,向加载器反馈处理代码背后的距离可能会有所帮助(如果它紧随其后,返回较小的块 - 如果它落后,则返回更大的块)。从理论上讲,这可以通过loader<T>
中的反向渠道进行安排。
既然我已经玩了上面的一点,可能更合适的是:
#include <iostream>
#include <future>
#include <functional>
#include <vector>
#include <memory>
// if it returns empty, there is nothing more to load:
template<class T>
using loader = std::function< std::vector<T>() >;
template<class T>
struct next_data;
template<class T>
struct streamer {
std::vector<T> data;
std::unique_ptr<next_data<T>> next;
};
template<class T>
struct next_data:std::future<streamer<T>> {
using parent = std::future<streamer<T>>;
using parent::parent;
next_data( parent&& o ):parent(std::move(o)){}
};
live example。它需要一些基础设施来填充第一个streamer<T>
,但代码将更简单,并且奇怪的要求(知道有多少数据,只从第一个元素做.get()
)就消失了。
template<class T>
streamer<T> stream_step( loader<T> l ) {
streamer<T> retval;
retval.data = l();
if (retval.data.empty())
return retval;
retval.next.reset( new next_data<T>(std::async( std::launch::async, [l](){ return stream_step(l); })));
return retval;
}
template<class T>
streamer<T> start_stream( loader<T> l ) {
streamer<T> retval;
retval.next.reset( new next_data<T>(std::async( std::launch::async, [l](){ return stream_step(l); })));
return retval;
}
缺点是编写基于范围的迭代器变得有点棘手。
以下是第二种实现的示例用法:
struct counter {
std::size_t max;
std::size_t current = 0;
counter( std::size_t m ):max(m) {}
std::vector<int> operator()() {
std::vector<int> retval;
std::size_t do_at_most = 100;
while( current < max && (do_at_most-->0)) {
retval.push_back( int(current) );
++current;
}
return retval;
}
};
int main() {
streamer<int> s = start_stream<int>( counter(1024) );
while(true) {
for (int x : s.data) {
std::cout << x << ",";
}
std::cout << std::endl;
if (!s.next)
break;
s = std::move(s.next->get());
}
// your code goes here
return 0;
}
其中counter
是一个简单的加载器(一个将数据读入std::vector<T>
的对象,无论大小如何)。数据的处理在main
代码中,我们只需将其打印成大小合适的块。
加载发生在不同的线程中,并且无论主线程做什么都会异步地继续。主线程只是按照它们的意愿传递std::vector<T>
。在您的情况下,您需要T
Mat
。
答案 1 :(得分:1)
Mat对象是具有内部内存分配的复杂对象。当你清除向量时,需要遍历包含Mat的每个实例并运行它的析构函数,这本身就是一个非常重要的操作。
还要记住,免费存储内存是非常重要的,因此根据您的堆实现,堆可能决定合并单元格等。
如果这是一个问题,你应该通过一个探查器清楚地找到瓶颈的位置。
答案 2 :(得分:-1)
小心使用优化,它可以让调试器疯狂。 如果你要在一个函数中执行此操作并简单地让向量超出范围? 由于元素不是指针,我认为这样可行。