Reduce pvector delta_cell quota on reads. Add example that demonstrates why this is needed.
Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
parent
5858c9d5e0
commit
cd5e45bae2
2 changed files with 79 additions and 23 deletions
|
@ -8,8 +8,11 @@ Author: Leonardo de Moura
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
#include "pvector.h"
|
#include "pvector.h"
|
||||||
|
#include "timeit.h"
|
||||||
using namespace lean;
|
using namespace lean;
|
||||||
|
|
||||||
|
// #define PVECTOR_PERF_TEST
|
||||||
|
|
||||||
static void tst1() {
|
static void tst1() {
|
||||||
pvector<int> v;
|
pvector<int> v;
|
||||||
lean_assert(v.empty());
|
lean_assert(v.empty());
|
||||||
|
@ -96,10 +99,21 @@ static void tst2() {
|
||||||
driver(128, 1000, 10000, 0.2, 0.1);
|
driver(128, 1000, 10000, 0.2, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #define PVECTOR_PERF_TEST
|
static void tst3() {
|
||||||
#ifdef PVECTOR_PERF_TEST
|
timeit t(std::cout, "tst3");
|
||||||
#include "timeit.h"
|
unsigned N = 20000;
|
||||||
|
unsigned M = 20000;
|
||||||
|
pvector<int> v;
|
||||||
|
for (unsigned i = 0; i < N; i++) v.push_back(i);
|
||||||
|
pvector<int> v2 = v;
|
||||||
|
for (unsigned i = 0; i < N / 2; i++) v2.push_back(i);
|
||||||
|
// v2 has a long trail of deltas
|
||||||
|
// Now, we only read v2
|
||||||
|
unsigned s = 0;
|
||||||
|
for (unsigned i = 0; i < M; i++) { s += v2[i % v2.size()]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PVECTOR_PERF_TEST
|
||||||
static void perf_vector(unsigned n) {
|
static void perf_vector(unsigned n) {
|
||||||
std::vector<int> q;
|
std::vector<int> q;
|
||||||
for (unsigned i = 0; i < n; i++) {
|
for (unsigned i = 0; i < n; i++) {
|
||||||
|
@ -173,6 +187,7 @@ static void tst_perf2() {
|
||||||
int main() {
|
int main() {
|
||||||
tst1();
|
tst1();
|
||||||
tst2();
|
tst2();
|
||||||
|
tst3();
|
||||||
#ifdef PVECTOR_PERF_TEST
|
#ifdef PVECTOR_PERF_TEST
|
||||||
tst_perf1();
|
tst_perf1();
|
||||||
tst_perf2();
|
tst_perf2();
|
||||||
|
|
|
@ -30,6 +30,7 @@ class pvector {
|
||||||
cell_kind m_kind;
|
cell_kind m_kind;
|
||||||
MK_LEAN_RC();
|
MK_LEAN_RC();
|
||||||
cell(cell_kind k):m_kind(k), m_rc(0) {}
|
cell(cell_kind k):m_kind(k), m_rc(0) {}
|
||||||
|
cell(cell const & c):m_kind(c.m_kind), m_rc(0) {}
|
||||||
void dealloc();
|
void dealloc();
|
||||||
unsigned size() const;
|
unsigned size() const;
|
||||||
/**
|
/**
|
||||||
|
@ -59,6 +60,10 @@ class pvector {
|
||||||
lean_assert(m_prev);
|
lean_assert(m_prev);
|
||||||
m_prev->inc_ref();
|
m_prev->inc_ref();
|
||||||
}
|
}
|
||||||
|
delta_cell(delta_cell const & c):cell(c), m_size(c.m_size), m_quota(c.m_quota), m_prev(c.m_prev) {
|
||||||
|
lean_assert(m_prev);
|
||||||
|
m_prev->inc_ref();
|
||||||
|
}
|
||||||
~delta_cell() { lean_assert(m_prev); m_prev->dec_ref(); }
|
~delta_cell() { lean_assert(m_prev); m_prev->dec_ref(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,6 +73,7 @@ class pvector {
|
||||||
*/
|
*/
|
||||||
struct pop_cell : public delta_cell {
|
struct pop_cell : public delta_cell {
|
||||||
pop_cell(cell * prev):delta_cell(cell_kind::PopBack, prev->size() - 1, prev) {}
|
pop_cell(cell * prev):delta_cell(cell_kind::PopBack, prev->size() - 1, prev) {}
|
||||||
|
pop_cell(pop_cell const & c):delta_cell(c) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,6 +83,7 @@ class pvector {
|
||||||
struct push_cell : public delta_cell {
|
struct push_cell : public delta_cell {
|
||||||
T m_val;
|
T m_val;
|
||||||
push_cell(T const & v, cell * prev):delta_cell(cell_kind::PushBack, prev->size() + 1, prev), m_val(v) {}
|
push_cell(T const & v, cell * prev):delta_cell(cell_kind::PushBack, prev->size() + 1, prev), m_val(v) {}
|
||||||
|
push_cell(push_cell const & c):delta_cell(c), m_val(c.m_val) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,6 +94,7 @@ class pvector {
|
||||||
unsigned m_idx;
|
unsigned m_idx;
|
||||||
T m_val;
|
T m_val;
|
||||||
set_cell(unsigned i, T const & v, cell * prev):delta_cell(cell_kind::Set, prev->size(), prev), m_idx(i), m_val(v) {}
|
set_cell(unsigned i, T const & v, cell * prev):delta_cell(cell_kind::Set, prev->size(), prev), m_idx(i), m_val(v) {}
|
||||||
|
set_cell(set_cell const & c):delta_cell(c), m_idx(c.m_idx), m_val(c.m_val) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
static push_cell & to_push(cell * c) { lean_assert(c->kind() == cell_kind::PushBack); return *static_cast<push_cell*>(c); }
|
static push_cell & to_push(cell * c) { lean_assert(c->kind() == cell_kind::PushBack); return *static_cast<push_cell*>(c); }
|
||||||
|
@ -101,6 +109,9 @@ class pvector {
|
||||||
cell * m_ptr;
|
cell * m_ptr;
|
||||||
pvector(cell * d):m_ptr(d) { lean_assert(m_ptr); m_ptr->inc_ref(); }
|
pvector(cell * d):m_ptr(d) { lean_assert(m_ptr); m_ptr->inc_ref(); }
|
||||||
|
|
||||||
|
/** \brief Return true iff the cell associated with this vector is shared */
|
||||||
|
bool is_shared() const { return m_ptr->get_rc() > 1; }
|
||||||
|
|
||||||
/** \brief Update the cell (and reference counters) of this vector */
|
/** \brief Update the cell (and reference counters) of this vector */
|
||||||
void update_cell(cell * new_cell) {
|
void update_cell(cell * new_cell) {
|
||||||
lean_assert(new_cell);
|
lean_assert(new_cell);
|
||||||
|
@ -110,29 +121,26 @@ class pvector {
|
||||||
m_ptr = new_cell;
|
m_ptr = new_cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Return true iff the cell associated with this vector is shared */
|
|
||||||
bool is_shared() const { return m_ptr->get_rc() > 1; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\brief Auxiliary method for \c flat_if_needed.
|
\brief Auxiliary method for \c flat
|
||||||
Given an empty vector \c r, then <tt>flat(c, r)</tt> will
|
Given an empty vector \c r, then <tt>flat_core(c, r)</tt> will
|
||||||
store in r the vector represented by cell \c c.
|
store in r the vector represented by cell \c c.
|
||||||
That is, the vector obtained after finding the root cell (aka wrapper for std::vector),
|
That is, the vector obtained after finding the root cell (aka wrapper for std::vector),
|
||||||
and applying all deltas.
|
and applying all deltas.
|
||||||
*/
|
*/
|
||||||
static void flat(cell * c, std::vector<T> & r) {
|
static void flat_core(cell * c, std::vector<T> & r) {
|
||||||
lean_assert(r.empty());
|
lean_assert(r.empty());
|
||||||
switch(c->kind()) {
|
switch(c->kind()) {
|
||||||
case cell_kind::PushBack:
|
case cell_kind::PushBack:
|
||||||
flat(to_push(c).m_prev, r);
|
flat_core(to_push(c).m_prev, r);
|
||||||
r.push_back(to_push(c).m_val);
|
r.push_back(to_push(c).m_val);
|
||||||
break;
|
break;
|
||||||
case cell_kind::PopBack:
|
case cell_kind::PopBack:
|
||||||
flat(to_pop(c).m_prev, r);
|
flat_core(to_pop(c).m_prev, r);
|
||||||
r.pop_back();
|
r.pop_back();
|
||||||
break;
|
break;
|
||||||
case cell_kind::Set:
|
case cell_kind::Set:
|
||||||
flat(to_set(c).m_prev, r);
|
flat_core(to_set(c).m_prev, r);
|
||||||
r[to_set(c).m_idx] = to_set(c).m_val;
|
r[to_set(c).m_idx] = to_set(c).m_val;
|
||||||
break;
|
break;
|
||||||
case cell_kind::Root:
|
case cell_kind::Root:
|
||||||
|
@ -141,6 +149,18 @@ class pvector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Change the representation to a root cell.
|
||||||
|
*/
|
||||||
|
void flat() {
|
||||||
|
lean_assert(m_ptr->kind() != cell_kind::Root);
|
||||||
|
std::vector<T> r;
|
||||||
|
flat_core(m_ptr, r);
|
||||||
|
update_cell(new root_cell());
|
||||||
|
to_root(m_ptr).m_vector.swap(r);
|
||||||
|
lean_assert(!is_shared());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\brief If the quota associated with m_cell is zero, then
|
\brief If the quota associated with m_cell is zero, then
|
||||||
compute a flat representation. That is, represent the vector
|
compute a flat representation. That is, represent the vector
|
||||||
|
@ -149,11 +169,29 @@ class pvector {
|
||||||
void flat_if_needed() {
|
void flat_if_needed() {
|
||||||
lean_assert(m_ptr->kind() != cell_kind::Root);
|
lean_assert(m_ptr->kind() != cell_kind::Root);
|
||||||
if (static_cast<delta_cell*>(m_ptr)->m_quota == 0) {
|
if (static_cast<delta_cell*>(m_ptr)->m_quota == 0) {
|
||||||
std::vector<T> r;
|
flat();
|
||||||
flat(m_ptr, r);
|
}
|
||||||
update_cell(new root_cell());
|
}
|
||||||
to_root(m_ptr).m_vector.swap(r);
|
|
||||||
lean_assert(!is_shared());
|
/** \brief Update quota based on the cost of a read */
|
||||||
|
void update_quota_on_read(unsigned cost) {
|
||||||
|
cost /= 2; // reads are cheaper than writes
|
||||||
|
if (cost > 0) {
|
||||||
|
if (cost >= m_ptr->quota()) {
|
||||||
|
flat();
|
||||||
|
} else {
|
||||||
|
if (is_shared()) {
|
||||||
|
switch (m_ptr->kind()) {
|
||||||
|
case cell_kind::PushBack: update_cell(new push_cell(to_push(m_ptr))); break;
|
||||||
|
case cell_kind::PopBack: update_cell(new pop_cell(to_pop(m_ptr))); break;
|
||||||
|
case cell_kind::Set: update_cell(new set_cell(to_set(m_ptr))); break;
|
||||||
|
case cell_kind::Root: lean_unreachable(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lean_assert(!is_shared());
|
||||||
|
lean_assert(cost < static_cast<delta_cell*>(m_ptr)->m_quota);
|
||||||
|
static_cast<delta_cell*>(m_ptr)->m_quota -= cost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,30 +221,33 @@ public:
|
||||||
/**
|
/**
|
||||||
\brief Access specified element
|
\brief Access specified element
|
||||||
\pre i < size()
|
\pre i < size()
|
||||||
|
|
||||||
TODO: Consider if we should reduce m_quota
|
|
||||||
reading. Another possibility is to
|
|
||||||
have a maximum number of delta_cells
|
|
||||||
*/
|
*/
|
||||||
T const & operator[](unsigned i) const {
|
T const & operator[](unsigned i) const {
|
||||||
lean_assert(i < size());
|
lean_assert(i < size());
|
||||||
cell const * it = m_ptr;
|
cell const * it = m_ptr;
|
||||||
|
unsigned cost = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (it->kind()) {
|
switch (it->kind()) {
|
||||||
case cell_kind::PushBack:
|
case cell_kind::PushBack:
|
||||||
if (i + 1 == to_push(it).m_size)
|
if (i + 1 == to_push(it).m_size) {
|
||||||
|
const_cast<pvector*>(this)->update_quota_on_read(cost);
|
||||||
return to_push(it).m_val;
|
return to_push(it).m_val;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case cell_kind::PopBack:
|
case cell_kind::PopBack:
|
||||||
break;
|
break;
|
||||||
case cell_kind::Set:
|
case cell_kind::Set:
|
||||||
if (to_set(it).m_idx == i)
|
if (to_set(it).m_idx == i) {
|
||||||
|
const_cast<pvector*>(this)->update_quota_on_read(cost);
|
||||||
return to_set(it).m_val;
|
return to_set(it).m_val;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case cell_kind::Root:
|
case cell_kind::Root:
|
||||||
|
const_cast<pvector*>(this)->update_quota_on_read(cost);
|
||||||
return to_root(it).m_vector[i];
|
return to_root(it).m_vector[i];
|
||||||
}
|
}
|
||||||
it = static_cast<delta_cell const *>(it)->m_prev;
|
it = static_cast<delta_cell const *>(it)->m_prev;
|
||||||
|
cost++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue