很抱歉,标题含糊。本质上,我正在尝试批准C ++驱动程序的时间(和总体)效率:
使用ifstream逐行读取文件
对我的程序至关重要的是,行要分开处理,因此我目前有4个单独的调用getline。
该程序使用string-stream将字符串行读取为整数向量。
最后,它将向量转换为整数的链接列表。有没有一种方法或函数可以将文件中的整数直接读取到ll个整数中?
以下是驱动程序代码:
@Autowired
private RestTemplate myRestTemplate;
这是一个小的示例输入:
int main(int argc, char *argv[])
{
ifstream infile(argv[1]);
vector<int> vals_add;
vector<int> vals_remove;
//Driver Code
if(infile.is_open()){
string line;
int n;
getline(infile, line);
istringstream iss (line);
getline(infile, line);
istringstream iss2 (line);
while (iss2 >> n){
vals_add.push_back(n);
}
getline(infile, line);
istringstream iss3 (line);
getline(infile, line);
istringstream iss4 (line);
while (iss4 >> n){
vals_remove.push_back(n);
}
int array_add[vals_add.size()];
copy(vals_add.begin(), vals_add.end(), array_add);
int array_remove[vals_remove.size()];
copy(vals_remove.begin(), vals_remove.end(), array_remove);
Node *ptr = CnvrtVectoList(array_add, sizeof(array_add)/sizeof(int));
print(ptr);
cout << "\n";
for(int i = 0; i < vals_remove.size(); i++){
deleteNode(&ptr, vals_remove[i]);
}
print(ptr);
cout << "\n";
}
第2行和第4行必须作为单独的列表进行处理,而第1行和第3行是列表的大小(它们必须动态分配内存,因此大小必须与输入保持一致)。
答案 0 :(得分:1)
有很多可以改进的地方。
首先,删除不必要的代码:您未使用iss
和iss3
。接下来,您的array_add
和array_remove
似乎是多余的。直接使用向量。
如果您大致了解平均可以读取多少个值,请在向量中保留空间以避免重复调整大小和复制(实际上,您似乎在输入中包含了这些数字; 使用而不是将其丢弃!)。您还可以将while
的阅读循环替换为std::copy
和std::istream_iterator
。
您还没有展示CnvrtVectoList
的实现方式,但是通常链表由于缺乏本地性而使用起来并不是特别有效:它们会在整个堆中抛出数据。连续的容器(=向量)几乎总是更有效,即使您需要删除中间的元素也是如此。尝试改用向量,并仔细计时性能。
最后,您可以排序这些值吗?如果是这样,则可以使用对std::lower_bound
的迭代调用或对std::set_difference
的单个调用来更有效地实现值的删除。
如果(并且仅当!)开销实际上是从文件中读取数字,因此请重新构造IO代码,不要单独读取行(这样可以避免很多冗余分配)。相反,请直接扫描输入文件(可以选择使用缓冲区或内存映射),并手动跟踪遇到的换行符。然后,您可以使用strtod
系列函数来扫描输入读取缓冲区中的数字。
或者,如果可以假定输入正确,则可以避免使用文件中提供的信息来读取单独的行:
int add_num;
infile >> add_num;
std::copy_n(std::istream_iterator<int>(infile), std::inserter(your_list, std::end(your_list));
int del_num;
infile >> del_num;
std::vector<int> to_delete(del_num);
std::copy_n(std::istream_iterator<int>(infile), del_num, to_delete.begin());
for (auto const n : del_num) {
deleteNode(&ptr, n);
}
答案 1 :(得分:0)
首先:为什么要使用一些自定义列表数据结构?它很半熟,即不支持分配器,因此很难更好地适应性能。只需将std::list
用于双向链接列表,或将std::forward_list
用于单链接列表。容易。
您似乎暗示了几个要求:
T
类型的值(例如:int
)将存储在链接列表中-std::list<T>
或std::forward_list<T>
(不是 Node
的原始列表)。
不应不必要地复制数据-即,不应重新分配内存块。
解析应该是可并行的,尽管这仅在I / O不会占用CPU时间的快速数据源上才有意义。
当时的想法是:
使用自定义分配器在可存储多个列表节点的连续段中划分内存。
将整个文件解析为使用上述分配器的链表。该列表将按需分配内存段。每条换行符都会开始一个新列表。
返回第二和第四列表(即第二和第四行中的元素列表)。
值得注意的是,包含元素计数的行是不必要的。当然,该数据可以传递给分配器以预分配足够的内存段,但这不允许并行化,因为并行解析器不知道元素计数在哪里-只有在对并行解析的数据进行协调后才能找到这些元素。是的,只需进行少量修改,就可以完全并行化该解析。那太酷了!
让我们从简单到最小开始:解析文件以生成两个列表。以下示例在数据集的内部生成的文本视图上使用了std::istringstream
,但是parse
当然也可以通过std::ifstream
传递。
// https://github.com/KubaO/stackoverflown/tree/master/questions/linked-list-allocator-58100610
#include <forward_list>
#include <iostream>
#include <sstream>
#include <vector>
using element_type = int;
template <typename allocator> using list_type = std::forward_list<element_type, allocator>;
template <typename allocator>
std::vector<list_type<allocator>> parse(std::istream &in, allocator alloc)
{
using list_t = list_type<allocator>;
std::vector<list_t> lists;
element_type el;
list_t *list = {};
do {
in >> el;
if (in.good()) {
if (!list) list = &lists.emplace_back(alloc);
list->push_front(std::move(el));
}
while (in.good()) {
int c = in.get();
if (!isspace(c)) {
in.unget();
break;
}
else if (c=='\n') list = {};
}
} while (in.good() && !in.eof());
for (auto &list : lists) list.reverse();
return lists;
}
然后进行测试:
const std::vector<std::vector<element_type>> test_data = {
{6, 18, 5, 20, 48, 2, 97},
{3, 6, 9, 12, 28, 5, 7, 10}
};
template <typename allocator = std::allocator<element_type>>
void test(const std::string &str, allocator alloc = {})
{
std::istringstream input{str};
auto lists = parse(input, alloc);
assert(lists.size() == 4);
lists.erase(lists.begin()+2); // remove the 3rd list
lists.erase(lists.begin()+0); // remove the 1st list
for (int i = 0; i < test_data.size(); i++)
assert(std::equal(test_data[i].begin(), test_data[i].end(), lists[i].begin()));
}
std::string generate_input()
{
std::stringstream s;
for (auto &data : test_data) {
s << data.size() << "\n";
for (const element_type &el : data) s << el << " ";
s << "\n";
}
return s.str();
}
现在,让我们来看一个自定义分配器:
class segment_allocator_base
{
protected:
static constexpr size_t segment_size = 128;
using segment = std::vector<char>;
struct free_node {
free_node *next;
free_node() = delete;
free_node(const free_node &) = delete;
free_node &operator=(const free_node &) = delete;
free_node *stepped_by(size_t element_size, int n) const {
auto *p = const_cast<free_node*>(this);
return reinterpret_cast<free_node*>(reinterpret_cast<char*>(p) + (n * element_size));
}
};
struct segment_store {
size_t element_size;
free_node *free = {};
explicit segment_store(size_t element_size) : element_size(element_size) {}
std::forward_list<segment> segments;
};
template <typename T> static constexpr size_t size_for() {
constexpr size_t T_size = sizeof(T);
constexpr size_t element_align = std::max(alignof(free_node), alignof(T));
constexpr auto padding = T_size % element_align;
return T_size + padding;
}
struct pimpl {
std::vector<segment_store> stores;
template <typename T> segment_store &store_for() {
constexpr size_t element_size = size_for<T>();
for (auto &s : stores)
if (s.element_size == element_size) return s;
return stores.emplace_back(element_size);
}
};
std::shared_ptr<pimpl> dp{new pimpl};
};
template<typename T>
class segment_allocator : public segment_allocator_base
{
segment_store *d = {};
static constexpr size_t element_size = size_for<T>();
static free_node *advanced(free_node *p, int n) { return p->stepped_by(element_size, n); }
static free_node *&advance(free_node *&p, int n) { return (p = advanced(p, n)); }
void mark_free(free_node *free_start, size_t n)
{
auto *p = free_start;
for (; n; n--) p = (p->next = advanced(p, 1));
advanced(p, -1)->next = d->free;
d->free = free_start;
}
public:
using value_type = T;
using pointer = T*;
template <typename U> struct rebind {
using other = segment_allocator<U>;
};
segment_allocator() : d(&dp->store_for<T>()) {}
segment_allocator(segment_allocator &&o) = default;
segment_allocator(const segment_allocator &o) = default;
segment_allocator &operator=(const segment_allocator &o) {
dp = o.dp;
d = o.d;
return *this;
}
template <typename U> segment_allocator(const segment_allocator<U> &o) :
segment_allocator_base(o), d(&dp->store_for<T>()) {}
pointer allocate(const size_t n) {
if (n == 0) return {};
if (d->free) {
// look for a sufficiently long contiguous region
auto **base_ref = &d->free;
auto *base = *base_ref;
do {
auto *p = base;
for (auto need = n; need; need--) {
auto *const prev = p;
auto *const next = prev->next;
advance(p, 1);
if (need > 1 && next != p) {
base_ref = &(prev->next);
base = next;
break;
} else if (need == 1) {
*base_ref = next; // remove this region from the free list
return reinterpret_cast<pointer>(base);
}
}
} while (base);
}
// generate a new segment, guaranteed to contain enough space
size_t count = std::max(n, segment_size);
auto &segment = d->segments.emplace_front(count);
auto *const start = reinterpret_cast<free_node*>(segment.data());
if (count > n)
mark_free(advanced(start, n), count - n);
else
d->free = nullptr;
return reinterpret_cast<pointer>(start);
}
void deallocate(pointer ptr, std::size_t n) {
mark_free(reinterpret_cast<free_node*>(ptr), n);
}
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
};
对于我们得到的少量测试数据,分配器只会分配一个段……一次!
要测试:
int main()
{
auto test_input_str = generate_input();
std::cout << test_input_str << std::endl;
test(test_input_str);
test<segment_allocator<element_type>>(test_input_str);
return 0;
}
并行化将利用上面的分配器,启动多个线程,并且在每次调用自己的分配器上的parse
时,每个解析器都从文件中的不同点开始。解析完成后,分配器将必须合并其段列表,以便它们比较相等。此时,可以使用常规方法将链接列表组合在一起。除了线程启动开销之外,并行化的开销可以忽略不计,并且不存在涉及将数据进行并行后组合的数据复制。但是我把这个练习留给读者。