luajit啥时候有的
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luajit啥时候有的相关的知识,希望对你有一定的参考价值。
参考技术A LuaJIT是一种高性能的Lua虚拟机,它是一个开放源代码的软件,由Mike Pall开发,旨在提高Lua的性能。LuaJIT的目标是提供一个高性能的Lua虚拟机,它可以比标准Lua虚拟机提供更快的执行速度。LuaJIT的主要特点是它的JIT编译器,它可以把Lua代码编译成机器码,从而提高执行速度。LuaJIT还提供了一个可移植的内存分配器,可以更有效地管理内存,从而提高性能。此外,LuaJIT还支持一些其他功能,例如垃圾回收、多线程编程等。选择啥标准容器(如果有的话)?
【中文标题】选择啥标准容器(如果有的话)?【英文标题】:what standard container to pick (if any)?选择什么标准容器(如果有的话)? 【发布时间】:2014-04-12 11:58:59 【问题描述】:我需要一些内存管理,并希望我可以基于一些 std
容器。我的要求是:
-
元素只有一个默认构造函数(没有复制,没有移动,没有别的)
容器可以(在后面)扩展一小块连续的元素
我什至大致知道我总共需要多少元素,甚至更好的是,在任何时候我最终需要多少元素。不过,这些只是估计值。
我并不真正需要迭代器,但获取元素运行编号的方法会很方便。
所以,我需要一些可以通过添加块来扩展的东西,比如std::deque
。但是对于std::deque
,我不能保证扩展,比如说,8 个元素,会给我一个连续的块。而std::deque
没有capacity
,所以我无法从std::deque
中“适应”。
这意味着我必须自己写,对吗? (注意:我不想知道如何自己写,但前提是我必须这样做)。
编辑澄清:只有每次扩展获得的元素块必须是连续的,而不是整个容器 - 这显然与其他要求相矛盾。
为 jalf 编辑 那么这是做什么用的:用于“排序”3D 点的空间八叉树。树节点指的是立方体单元,并形成一个链接结构,父节点和子节点使用指针链接。兄弟节点没有链接,但在内存中是相邻的。事先不知道节点的总数(因为每个最终节点的点数>1),但可以得到一个估计值。在树的构建过程中,在划分非最终节点时,必须获得最多 8 个新节点的连续块,然后将其链接到树中。移动或复制这些节点会使指向它们的任何现有链接(指针)失效。
另一个编辑只是为了澄清一些讨论。任何基于std::vector<T>
的设计都不得使用resize()
和/或reserve()
。在某些条件下,两者都需要 T
的复制或移动构造函数。即使从未在这些条件下调用,代码也不会编译。
【问题讨论】:
对于#2,你是说只有“新”元素必须是连续的吗?如果您有一个包含 10 个元素的容器,并将其扩展为 14 号,那么只有最后 4 个必须是连续的? 您不能有连续、可扩展和无复制。选择两个。list<vector<T>>
? (在向量上小心使用reserve
。)
在这种情况下,我想我会定义一个包装 std::container 的结构,例如向量。外部结构应该包含一个 size 成员,因为您可能不需要调整各个元素的大小。那么总容量就是简单的总和。这很容易让人联想到您如何设计带有描述单个块的偏移量和大小的标题的文件格式。如果您需要灵活的压缩文件格式,我可以推荐使用 HPF5
@Walter: vector::reserve
对元素没有要求。也许你在想resize
?即便如此,resize
现在只需要可移动,不可复制。你的物品可以移动吗?
【参考方案1】:
只要一抓,std::vector
就是给你的。
它是完全连续的,不仅仅是在块中,并且可以扩展,保持连续(第 2 点)。扩展可能意味着重新分配(因此先前获得的指针/迭代器无效,以及移动),但如果您事先知道总大小(第 3 点),您可以reserve()
这样就不会发生重新分配.
给定一个向量v
的迭代器i
,可以通过i - v.begin()
得到一个运行号(位置);同样,通过p - &v[0]
(第 4 点)给定一个指向元素的指针 p
。
关键是您的观点 1。有 emplace_back()
,但出于与异常安全相关的原因,std::vector
仍会尝试在某处临时构造元素,然后将它们移动到它们的永久位置。
假设你有这个类
struct A
A()
A(A&&) = delete;
A(const A&) = delete;
;
我可以看到两种解决方法:
派生另一个类B
默认构造而不是复制/移动构造:
struct B : A
B() : A()
B(B&&) : A()
B(const B&) : A()
;
如果你不能这样做,那么就为你创建一个分配器对象:
template<typename T>
struct allocator : std::allocator<T>
using std::allocator<T>::allocator;
using std::allocator<T>::construct;
template<typename U>
void construct(U* p, U&&) construct(p);
template<typename U>
void construct(U* p, const U&) construct(p);
template<typename U>
struct rebind using other = allocator<U>; ;
;
template<>
struct allocator<void> : std::allocator<void> ;
这两种情况的使用如下图所示 (live example):
template<typename C, size_t N = 100>
void test()
C c;
c.reserve(N);
for (size_t i = 0; i < N; ++i)
c.emplace_back();
int main ()
test<std::vector<B> >();
test<std::vector<A, allocator <A> > >();
请记住,这种方式仍然有 A
的实例被构造然后丢弃。这是使用std::vector
的不幸后果。如果A
足够小并且它的默认构造没有任何奇怪的副作用,这应该不是问题。
如果您仍然需要在初始 reserve()
之外进行扩展,那么我建议将此类向量的容器用作块。如果您仍然想将此元容器视为具有自己的迭代器的单个容器,那么相关的是我自己的 join
view 和它的 iterator 只是为了一个想法,但这仍然是非常实验性的。我敢打赌,Boost 中也有为此目的的东西,但我不是很熟悉。
【讨论】:
我不喜欢这样,因为我想避免重新调整大小的成本(如果容量太小则需要)。 很公平,但要先测量。我的最后一个建议是:制作另一个包含向量作为块的容器,如上所述。这可能很容易或很难(或非常困难),具体取决于其他要求(例如,如果您想要迭代器、随机访问等)。 事实证明,即使在复制的情况下,std::vector
的性能也几乎优于其他所有方法,因为它对缓存非常友好(其中std::list
是缓存敌对的,std::deque
并不比 @987654346 好多少@) 并且通常分配内存的频率低于替代方案。在声明 std::vector
太慢之前,您真的想进行分析。【参考方案2】:
我建议只使用 std::deque<std::vector<T>>
作为自定义类的私有数据成员,以确保:
【讨论】:
我想到了一个使用std::list<std::vector<T>>
的设计。
deque
具有随机访问权限,而 list
没有; deque
也更紧凑(在内存中)。在你的情况下,它只是更好。
它更紧凑,但所有这些(最初是空的)向量都在堆上。我可能只需要几个向量,也许只需要一个(如果我最初对节点数的猜测没问题的话)。
@Walter:一个空向量应该只有 24 个字节值(在 64 位平台上),但是如果您对 list
感到满意,请继续。
我只是尝试使用sequence_container<std::vector<T>>
设计来实现这一点,但偶然发现std::vector::reserve()
需要可复制构造的元素的问题......所以我不能使用reserve()
- 我应该怎么做代替吗?【参考方案3】:
所有复杂的解决方案是怎么回事? std::vector<std::unique_ptr<T>>
不会解决问题,还是我遗漏了什么?
【讨论】:
这会起作用,但代价是每个元素都有一个指针,并且元素本身的空间局部性可能很差,具体取决于用于它们的分配器。我认为 OP 在大多数情况下都需要连续的元素,并且可能不希望每个元素有 8 个字节(一个 unique_ptr)。 另一个问题是不能提供自定义分配器(至少不容易......和对应的删除器)【参考方案4】:这是一个简单的模板化 C++11 类,它使用指向向量的指针向量来包含元素,它在后面以块的形式扩展。指向先前分配的元素的指针在扩展后仍然有效。它支持基于范围的 for 循环和随机访问。我将 std::vector 用于两个目的,一个***向量保持指向第二级向量的指针,顶部向量用普通 push_back 扩展,因此每次到达第二级向量的指针都会被复制(非常快)扩展数的 2 的幂 - 指定为非常少且不频繁。二级向量分配有在构造 Chunky 实例时或在扩展期间给出的初始大小,并且大小永远不会改变,它们包含元素(模板的参数),因此指向元素的指针不会被调用extend()。
我为每个 100 万个元素的 10 次扩展和对每个元素的访问计时,它在 106 毫秒内运行(每次访问平均为 10.6 纳秒)。
(coliru link)
然后我为每个 10,000 个元素的 1000 个扩展和对每个元素的访问计时,它在 115 毫秒内运行(每次访问平均为 11.5 纳秒)。
(coliru link)
矮胖的.h
#ifndef CHUNKY_H
#define CHUNKY_H
#include <vector>
#include <cstddef>
#include <stdexcept>
template<class T> class Chunky
std::vector< std::vector<T> * > _chunk;
size_t _size = 0;
T* _active = nullptr;
size_t _begin = 0;
size_t _end = 0;
public:
Chunky() = default;
Chunky( size_t guess ) extend( guess );
~Chunky() for ( auto & it : _chunk ) delete it;
void extend( size_t x )
_chunk.push_back( new std::vector<T>( x ) );
_size += x;
size_t size() const return _size;
T & operator[]( size_t z ) return at( z );
T & at( size_t z )
if ( z < _begin || z >= _end )
size_t x = 0;
for( _end = 0; z >= _end && x < _chunk.size(); ++x)
_begin = _end;
_end += _chunk[x]->size();
_active = _chunk[x]->data();
if (z >= _end) throw std::out_of_range("Chunky at "+std::to_string(z));
return _active[z - _begin];
class iterator
: public std::iterator<std::forward_iterator_tag, int>
Chunky<T> * _tp = nullptr;
size_t _x = 0;
public:
iterator() = default;
iterator(Chunky<T> * tp) : _tp(tp)
bool operator!= (const iterator& other) const
return _tp != other._tp || _x != other._x;
T & operator* () const return _tp->at(_x);
T * operator->() const return &_tp->at(_x);
iterator& operator++ ()
if (++_x >= _tp->size())
_x = 0;
_tp = nullptr;
return *this;
iterator& operator+= ( size_t x ) while(x--) *this++; return *this;
friend inline iterator& operator+ ( iterator & lhs, size_t x ) while(x--) ++lhs; return lhs;
friend inline iterator operator+ ( iterator lhs, size_t x ) while(x--) ++lhs; return lhs;
;
inline iterator begin() return iterator(this);
static inline const iterator end() return iterator();
;
#endif
一个简单的测试
#include "chunky.h"
#include <iostream>
#include <chrono>
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
struct Element
int _value = 0;
Element() = default;
Element(const Element&) = delete;
Element(Element&&) = delete;
Element& operator=(Element const&) = delete;
;
int main( int argc, char *argv[] )
Chunky<Element> c( 5 ); // 5 default constructed Elements
int i = 0;
// Chunky allows range based for loop
for( auto & it : c ) it._value = 100 + i++;
// Pointers to elements are valid for the lifetime of the container
Element * element_ptr = & c[4]; // Take address of one of the elements
std::cout << "The fifth element has a value of " << element_ptr->_value << std::endl;
size_t previous_size = c.size();
c.extend( 10 ); // 10 more default constructed Elements
std::cout << "Dereferencing pointer to fifth element after extend, value is still " << element_ptr->_value << std::endl;
for( size_t k=0; k < 10; ++k )
c[previous_size + k]._value = 1100 + i++;
// random access to initial elements and newly extended elements
c[3]._value = -3;
c[13]._value = -13;
std::cout << "\nThe Elements contain values of: " << std::endl;
// range based for loop
for( auto & it : c ) std::cout << it._value << std::endl;
steady_clock::time_point start = steady_clock::now();
size_t extend_size = 1e6;
for( size_t x = 0; x < 10; ++x )
size_t previous_size = c.size();
c.extend( extend_size ); // 1 million more default constructed Elements
for( size_t k=0; k < extend_size; ++k )
c[previous_size + k]._value = previous_size + k;
steady_clock::time_point end = steady_clock::now();
std::cout << "\nExtending 10 times by " << extend_size << " and initializing the Elements took "
<< duration_cast<milliseconds>(end - start).count()
<< " msec.\n";
return 0;
示例运行
g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
The fifth element has a value of 104
Dereferencing pointer to fifth element after extend, value is still 104
The Elements contain values of:
100
101
102
-3
104
1105
1106
1107
1108
1109
1110
1111
1112
-13
1114
Extending 10 times by 1000000 and initializing the Elements took 106 msec.
【讨论】:
我想要的本质上是一个分配器,它可以避免每次我需要一些内存时调用new
。但是,您的实现正是这样做的:每次调用 extend()
时都调用 new。
是的,我想避免多次致电new
。此外,复制我的对象(如果它们在可扩展向量中则需要)会破坏我的代码。 std::deque
将是完美的,如果我能从中得到一个连续的对象块。
@Walter,我明白了。我应该指出我没有使用可扩展向量。我构造了一次向量,其大小赋予extend
成员函数,并且在Chunky
实例被破坏之前它们不会被破坏。 Element
类删除了复制构造函数、移动构造函数和赋值运算符。试一试。
@Walter 我应该澄清一下,我使用 std::vector 有两个目的,一个***向量来保持指向 2 级向量的指针,顶部向量用普通 push_back 扩展,所以指向的指针每当您达到扩展数量的 2 次方时(您说扩展数量很少且不常见),第二级向量就会被复制(非常快)。二级向量分配有初始大小并且大小永远不会改变,它们包含元素(模板的参数),因此指向元素的指针不会因调用 extend()
而无效。
你也可以每次都直接从堆中分配。这就是我想要避免的。【参考方案5】:
简短的回答似乎是没有单一的标准容器可以完成这项工作,您必须自己编写。
事实证明这比我想象的要困难,因为指向生成对象的指针必须在内存管理器的生命周期内保持有效(在原始问题中通过对象不能移动或复制的请求实现)。这不包括使用std::vector::resize()
和std::vector::reserve()
。 sequence_container<std::vector<T>>
设计仍然是可能的,但要么需要为每个新对象块创建另一个 vector
,要么预先构建整个对象块,然后将它们分发出去,直到块用完。
为了避免这种情况,似乎必须编写一些chunk
类(用来代替vector
)并处理allocator
问题。这是一个实现:
#include <list>
#include <memory>
template<typename type, typename allocator=std::allocator<type>,
template<typename,typename> class sequence_container = std::list>
class chunk_allocator
public:
using object = type;
using pointer = object*;
using size_type = std::size_t;
using alloc_traits = std::allocator_traits<allocator>;
private:
struct chunk
allocator alloc;
const pointer beg_data, end_capacity;
pointer end_data;
chunk(size_type cap, const allocator&all)
: alloc(all)
, beg_data(alloc_traits::allocate(alloc,cap))
, end_capacity(beg_data+cap)
, end_data(beg_data)
~chunk()
if(beg_data==nullptr) return;
for(; --end_data>=beg_data; --end_data)
alloc_traits::destroy(alloc,end_data);
alloc_traits::deallocate(alloc,beg_data,capacity());
size_type size() const noexcept return end_data - beg_data;
size_type capacity() const noexcept return end_capacity - beg_data;
pointer make(size_type n)
if(end_data + n > end_capacity)
return nullptr;
auto ptr = end_data;
for(; n; --n,++end_data)
alloc_traits::construct(alloc,end_data);
return ptr;
;
using chunk_alloc = typename alloc_traits::template rebind_alloc<chunk>;
using chunk_container = sequence_container<chunk,chunk_alloc>;
using chunk_iterator = typename chunk_container::iterator;
chunk_container chunks;
chunk_iterator last_chunk;
/// no default constructor
chunk_allocator() = delete;
/// no copy
chunk_allocator(chunk_allocator const&) = delete;
chunk_allocator&operator=(chunk_allocator const&) = delete;
public:
/// allow move
chunk_allocator(chunk_allocator&&) = default;
chunk_allocator&operator=(chunk_allocator&&) = default;
/// constructor
explicit
chunk_allocator(size_type initial_capacity, allocator const&alloc=allocator())
: chunks(alloc)
, last_chunk(chunks.emplace(chunks.end(),initial_capacity,alloc))
/// invalid index
static constexpr size_type invalid = ~size_type(0);
/// find index for element, return invalid if not ours
size_type index(const object*ptr) const noexcept
size_type n=0;
for(auto c=chunks.begin(); c!=chunks.end(); ++c)
if(c->beg_data <= ptr && ptr < c->end_data)
return n + size_type(ptr-c->beg_data);
else
n += c->size();
return invalid;
/// obtain contiguous chunks of objects
/// \param[in] n \# objects in returned chunk
/// \param[in] chunk_size \# objects to allocate should we not have enough
/// \return pointer to first of n contiguous objects
object*create(const size_type n, size_type chunk_size=0)
if(n==0)
return nullptr;
if(last_chunk->end_data + n > last_chunk->end_capacity)
if(chunk_size==0) chunk_size = last_chunk->capacity();
if(chunk_size< n) chunk_size = n;
last_chunk = chunks.emplace(chunks.end(),chunk_size,last_chunk->alloc);
return last_chunk->make(n);
;
// test
#include <iostream>
struct foo
int X;
static int C;
foo() : X(C++) std::cout<<"foo::foo(): X="<<X<<std::endl;
foo(foo const&) = delete;
foo&operator=(foo const&) = delete;
foo(foo &&) = delete;
foo&operator=(foo &&) = delete;
;
int foo::C=0;
int main()
std::cout<<" chunk_allocator<foo> C(3);"<<std::endl;
chunk_allocator<foo> C(3);
auto a = C.create(1);
std::cout<<" auto a=C.create(1)="<<a<<std::endl;
auto b = C.create(4);
std::cout<<" auto b=C.create(4)="<<b<<std::endl;
auto c = C.create(3);
std::cout<<" auto c=C.create(3)="<<c<<std::endl;
std::cout<<" a="<<a<<" a->X="<<a->X<<" index(a)="<<C.index(a)<<'\n'
<<" b="<<b<<" b->X="<<b->X<<" index(b)="<<C.index(b)<<'\n'
<<" c="<<c<<" c->X="<<c->X<<" index(c)="<<C.index(c)<<'\n';
【讨论】:
以上是关于luajit啥时候有的的主要内容,如果未能解决你的问题,请参考以下文章
最新最全为 iOS 和 Android 的真机和模拟器编译 Luajit 库