make the token page size be variable instead of fixed 8192

also changed the token-page layout a little bit to remove
a not-needed field.

This reduces the number of malloc()/free() calls substantially
with minimal increase in memory consumption (~2%).
For the tail of large sources, the number of malloc calls goes
typically from ~10000 to ~100 (e.g.: bryce_big.jpg: 22711 -> 105)

Change-Id: Ib847f41e618ed8c303d26b76da982fbc48de45b9
This commit is contained in:
skal 2014-05-05 14:26:14 -07:00
parent f948d08c81
commit 5fe628d35d
3 changed files with 38 additions and 20 deletions

View File

@ -27,23 +27,27 @@
#if !defined(DISABLE_TOKEN_BUFFER) #if !defined(DISABLE_TOKEN_BUFFER)
// we use pages to reduce the number of memcpy() // we use pages to reduce the number of memcpy()
#define MAX_NUM_TOKEN 8192 // max number of token per page #define MIN_PAGE_SIZE 8192 // minimum number of token per page
#define FIXED_PROBA_BIT (1u << 14) #define FIXED_PROBA_BIT (1u << 14)
typedef uint16_t token_t; // bit#15: bit
// bit #14: constant proba or idx
// bits 0..13: slot or constant proba
struct VP8Tokens { struct VP8Tokens {
uint16_t tokens_[MAX_NUM_TOKEN]; // bit#15: bit VP8Tokens* next_; // pointer to next page
// bit #14: constant proba or idx
// bits 0..13: slot or constant proba
VP8Tokens* next_;
}; };
// Token data is located in memory just after the next_ field.
// This macro is used to return their address and hide the trick.
#define TOKEN_DATA(p) ((token_t*)&(p)[1])
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void VP8TBufferInit(VP8TBuffer* const b) { void VP8TBufferInit(VP8TBuffer* const b, int page_size) {
b->tokens_ = NULL; b->tokens_ = NULL;
b->pages_ = NULL; b->pages_ = NULL;
b->last_page_ = &b->pages_; b->last_page_ = &b->pages_;
b->left_ = 0; b->left_ = 0;
b->page_size_ = (page_size < MIN_PAGE_SIZE) ? MIN_PAGE_SIZE : page_size;
b->error_ = 0; b->error_ = 0;
} }
@ -55,22 +59,26 @@ void VP8TBufferClear(VP8TBuffer* const b) {
WebPSafeFree((void*)p); WebPSafeFree((void*)p);
p = next; p = next;
} }
VP8TBufferInit(b); VP8TBufferInit(b, b->page_size_);
} }
} }
static int TBufferNewPage(VP8TBuffer* const b) { static int TBufferNewPage(VP8TBuffer* const b) {
VP8Tokens* const page = VP8Tokens* page = NULL;
b->error_ ? NULL : (VP8Tokens*)WebPSafeMalloc(1ULL, sizeof(*page)); const size_t size = sizeof(*page) + b->page_size_ * sizeof(token_t);
if (!b->error_) {
page = (VP8Tokens*)WebPSafeMalloc(1ULL, size);
}
if (page == NULL) { if (page == NULL) {
b->error_ = 1; b->error_ = 1;
return 0; return 0;
} }
page->next_ = NULL;
*b->last_page_ = page; *b->last_page_ = page;
b->last_page_ = &page->next_; b->last_page_ = &page->next_;
b->left_ = MAX_NUM_TOKEN; b->left_ = b->page_size_;
b->tokens_ = page->tokens_; b->tokens_ = TOKEN_DATA(page);
page->next_ = NULL;
return 1; return 1;
} }
@ -197,8 +205,9 @@ void VP8TokenToStats(const VP8TBuffer* const b, proba_t* const stats) {
while (p != NULL) { while (p != NULL) {
const int N = (p->next_ == NULL) ? b->left_ : 0; const int N = (p->next_ == NULL) ? b->left_ : 0;
int n = MAX_NUM_TOKEN; int n = MAX_NUM_TOKEN;
const token_t* const tokens = TOKEN_DATA(p);
while (n-- > N) { while (n-- > N) {
const uint16_t token = p->tokens_[n]; const token_t token = tokens[n];
if (!(token & FIXED_PROBA_BIT)) { if (!(token & FIXED_PROBA_BIT)) {
Record((token >> 15) & 1, stats + (token & 0x3fffu)); Record((token >> 15) & 1, stats + (token & 0x3fffu));
} }
@ -220,9 +229,10 @@ int VP8EmitTokens(VP8TBuffer* const b, VP8BitWriter* const bw,
while (p != NULL) { while (p != NULL) {
const VP8Tokens* const next = p->next_; const VP8Tokens* const next = p->next_;
const int N = (next == NULL) ? b->left_ : 0; const int N = (next == NULL) ? b->left_ : 0;
int n = MAX_NUM_TOKEN; int n = b->page_size_;
const token_t* const tokens = TOKEN_DATA(p);
while (n-- > N) { while (n-- > N) {
const uint16_t token = p->tokens_[n]; const token_t token = tokens[n];
const int bit = (token >> 15) & 1; const int bit = (token >> 15) & 1;
if (token & FIXED_PROBA_BIT) { if (token & FIXED_PROBA_BIT) {
VP8PutBit(bw, bit, token & 0xffu); // constant proba VP8PutBit(bw, bit, token & 0xffu); // constant proba
@ -245,9 +255,10 @@ size_t VP8EstimateTokenSize(VP8TBuffer* const b, const uint8_t* const probas) {
while (p != NULL) { while (p != NULL) {
const VP8Tokens* const next = p->next_; const VP8Tokens* const next = p->next_;
const int N = (next == NULL) ? b->left_ : 0; const int N = (next == NULL) ? b->left_ : 0;
int n = MAX_NUM_TOKEN; int n = b->page_size_;
const token_t* const tokens = TOKEN_DATA(p);
while (n-- > N) { while (n-- > N) {
const uint16_t token = p->tokens_[n]; const token_t token = tokens[n];
const int bit = token & (1 << 15); const int bit = token & (1 << 15);
if (token & FIXED_PROBA_BIT) { if (token & FIXED_PROBA_BIT) {
size += VP8BitCost(bit, token & 0xffu); size += VP8BitCost(bit, token & 0xffu);

View File

@ -363,12 +363,14 @@ typedef struct {
VP8Tokens* pages_; // first page VP8Tokens* pages_; // first page
VP8Tokens** last_page_; // last page VP8Tokens** last_page_; // last page
uint16_t* tokens_; // set to (*last_page_)->tokens_ uint16_t* tokens_; // set to (*last_page_)->tokens_
int left_; // how many free tokens left before the page is full. int left_; // how many free tokens left before the page is full
int page_size_; // number of tokens per page
#endif #endif
int error_; // true in case of malloc error int error_; // true in case of malloc error
} VP8TBuffer; } VP8TBuffer;
void VP8TBufferInit(VP8TBuffer* const b); // initialize an empty buffer // initialize an empty buffer
void VP8TBufferInit(VP8TBuffer* const b, int page_size);
void VP8TBufferClear(VP8TBuffer* const b); // de-allocate pages memory void VP8TBufferClear(VP8TBuffer* const b); // de-allocate pages memory
#if !defined(DISABLE_TOKEN_BUFFER) #if !defined(DISABLE_TOKEN_BUFFER)

View File

@ -258,7 +258,12 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config,
VP8EncInitLayer(enc); VP8EncInitLayer(enc);
#endif #endif
VP8TBufferInit(&enc->tokens_); // lower quality means smaller output -> we modulate a little the page
// size based on quality. This is just a crude 1rst-order prediction.
{
const float scale = 1.f + config->quality * 5.f / 100.f; // in [1,6]
VP8TBufferInit(&enc->tokens_, (int)(mb_w * mb_h * 4 * scale));
}
return enc; return enc;
} }