Ticket #326: avoid_string_operations.patch

File avoid_string_operations.patch, 20.0 KB (added by Richard Boulton, 15 years ago)

Patch to speed search by using a fixed size buffer for keys, rather than C++ strings

  • chert_postlist.h

     
    8383                Xapian::docid did, bool adding,
    8484                PostlistChunkReader ** from, PostlistChunkWriter **to);
    8585
     86#define KEYBUF_MAX_LEN CHERT_BTREE_MAX_KEY_LEN
     87        mutable char keybuf[KEYBUF_MAX_LEN];
     88        mutable size_t keybuf_curlen;
     89
    8690        /// Compose a key from a termname and docid.
    87         static string make_key(const string & term, Xapian::docid did) {
    88             string key = make_key(term);
    89             key += pack_uint_preserving_sort(did);
    90             return key;
     91        void make_key(const string & term, Xapian::docid did) const {
     92            keybuf_curlen = 0;
     93            make_key(term);
     94            keybuf_curlen += append_packed_uint_preserving_sort(
     95                keybuf + keybuf_curlen, KEYBUF_MAX_LEN - keybuf_curlen, did);
    9196        }
    9297
    9398        /// Compose a key from a termname.
    94         static string make_key(const string & term) {
     99        void make_key(const string & term) const {
    95100            // Special case for doclen lists.
    96             if (term.empty()) return string("\x00\xe0", 2);
     101            if (term.empty()) {
     102                keybuf[0] = '\x00';
     103                keybuf[1] = '\xe0';
     104                keybuf_curlen = 2;
     105                return;
     106            }
    97107
    98             return pack_string_preserving_sort(term);
     108            keybuf_curlen = append_string_preserving_sort(keybuf, KEYBUF_MAX_LEN, term);
    99109        }
    100110
    101111        bool term_exists(const string & term) const {
    102             return key_exists(make_key(term));
     112            make_key(term);
     113            return key_exists(keybuf, keybuf_curlen);
    103114        }
    104115
    105116        /** Returns number of docs indexed by @a term.
     
    129140         */
    130141        Xapian::Internal::RefCntPtr<const ChertDatabase> this_db;
    131142
     143        ChertPostListTable * pltable;
     144
    132145        /// The termname for this postlist.
    133146        string tname;
    134147
  • chert_table.h

     
    227227        memmove(p + I2 + K1, key_.data(), key_len);
    228228        set_component_of(1);
    229229    }
     230    void form_key(const char * key_, size_t keylen) {
     231        if (keylen > CHERT_BTREE_MAX_KEY_LEN) {
     232            // We check term length when a term is added to a document but
     233            // chert doubles zero bytes, so this can still happen for terms
     234            // which contain one or more zero bytes.
     235            std::string msg("Key too long: length was ");
     236            msg += om_tostring(keylen);
     237            msg += " bytes, maximum length of a key is "
     238                   STRINGIZE(CHERT_BTREE_MAX_KEY_LEN) " bytes";
     239            throw Xapian::InvalidArgumentError(msg);
     240        }
     241
     242        set_key_len(keylen + K1 + C2);
     243        memmove(p + I2 + K1, key_, keylen);
     244        set_component_of(1);
     245    }
    230246    // FIXME passing cd here is icky
    231247    void set_tag(int cd, const char *start, int len, bool compressed) {
    232248        memmove(p + cd, start, len);
     
    396412         *          false if key is not found in table.
    397413         */
    398414        bool get_exact_entry(const std::string & key, std::string & tag) const;
     415        bool get_exact_entry(const char * key, size_t keylen, string & tag) const;
    399416
    400417        /** Check if a key exists in the Btree.
    401418         *
     
    409426         *          false if key is not found in table.
    410427         */
    411428        bool key_exists(const std::string &key) const;
     429        bool key_exists(const char * key, size_t keylen) const;
    412430
    413431        /** Read the tag value for the key pointed to by cursor C_.
    414432         *
     
    597615        void read_root();
    598616        void split_root(uint4 split_n);
    599617        void form_key(const std::string & key) const;
     618        void form_key(const char * key, size_t keylen) const;
    600619
    601620        char other_base_letter() const {
    602621           return (base_letter == 'A') ? 'B' : 'A';
  • chert_positionlist.cc

     
    7070              did << ", " << term);
    7171
    7272    string data;
    73     if (!get_exact_entry(pack_uint_preserving_sort(did) + term, data)) {
     73    string key;
     74    // 5 bytes is usually enough for a packed uint
     75    key.reserve(5 + term.size());
     76    append_packed_uint_preserving_sort(key, did);
     77    key.append(term);
     78    if (!get_exact_entry(key, data)) {
    7479        // There's no positional information for this term.
    7580        return 0;
    7681    }
     
    106111    positions.clear();
    107112
    108113    string data;
    109     if (!table->get_exact_entry(pack_uint_preserving_sort(did) + tname, data)) {
     114    string key;
     115    // 5 bytes is usually enough for a packed uint
     116    key.reserve(5 + tname.size());
     117    append_packed_uint_preserving_sort(key, did);
     118    key.append(tname);
     119    if (!table->get_exact_entry(key, data)) {
    110120        // There's no positional information for this term.
    111121        current_pos = positions.begin();
    112122        return false;
  • chert_positionlist.h

     
    3434
    3535class ChertPositionListTable : public ChertTable {
    3636    static string make_key(Xapian::docid did, const string & tname) {
    37         return pack_uint_preserving_sort(did) + tname;
     37        string result;
     38        // 5 bytes is usually enough for a packed uint
     39        result.reserve(5 + tname.size());
     40        append_packed_uint_preserving_sort(result, did);
     41        result.append(tname);
     42        return result + tname;
    3843    }
    3944
    4045  public:
  • chert_utils.h

     
    2626#include "omassert.h"
    2727
    2828#include <xapian/types.h>
     29#include <xapian/error.h>
    2930
    3031#include <string>
    3132
     
    224225 *  of 256 bytes on the length of the integer.  However, this is unlikely to
    225226 *  ever be a problem.
    226227 *
     228 *  @param result A string to append the representation of the integer to.
    227229 *  @param value  The integer to represent.
    228  *
    229  *  @result       A string containing the representation of the integer.
    230230 */
    231231template<class T>
    232 string
    233 pack_uint_preserving_sort(T value)
     232void
     233append_packed_uint_preserving_sort(string & result, T value)
    234234{
    235235    // Check unsigned
    236236    STATIC_ASSERT_UNSIGNED_TYPE(T);
     237    STATIC_ASSERT(sizeof(T) >= 4);
    237238
    238     string result;
     239    string::size_type start = result.size();
     240    if (value <= 0xffff) {
     241        if (value < 0xff) {
     242            // 1 byte
     243            char buf[2];
     244            buf[0] = char(1);
     245            buf[1] = char(value & 0xff);
     246            result.append(buf, 2);
     247            return;
     248        } else {
     249            // 2 bytes
     250            char buf[3];
     251            buf[0] = char(2);
     252            buf[1] = char((value >> 8) & 0xff);
     253            buf[2] = char(value & 0xff);
     254            result.append(buf, 3);
     255            return;
     256        }
     257    }
     258    if (value <= 0xffffff) {
     259        // 3 bytes
     260        char buf[4];
     261        buf[0] = char(3);
     262        buf[1] = char((value >> 16) & 0xff);
     263        buf[2] = char((value >> 8) & 0xff);
     264        buf[3] = char(value & 0xff);
     265        result.append(buf, 4);
     266        return;
     267    }
     268
     269    if (value <= 0xffffffff) {
     270        // 4 bytes.
     271        char buf[5];
     272        buf[0] = char(4);
     273        buf[1] = char((value >> 24) & 0xff);
     274        buf[2] = char((value >> 16) & 0xff);
     275        buf[3] = char((value >> 8) & 0xff);
     276        buf[4] = char(value & 0xff);
     277        result.append(buf, 5);
     278        return;
     279    }
     280
     281    if (sizeof(T) > 4) {
     282        if (result.capacity() < start + 9) {
     283            result.reserve(start + 9);
     284        }
     285        // More than 4 bytes. Do first 4, then loop.
     286        result.append(string::size_type(1u), char((value >> 24) & 0xff));
     287        result.append(string::size_type(1u), char((value >> 16) & 0xff));
     288        result.append(string::size_type(1u), char((value >> 8) & 0xff));
     289        result.append(string::size_type(1u), char(value & 0xff));
     290        value = value >> 16;
     291        value = value >> 16;
     292        while (value != 0) {
     293            om_byte part = static_cast<om_byte>(value & 0xff);
     294            value = value >> 8;
     295            result.insert(start, 1u, char(part));
     296        }
     297        result.insert(start, 1u, char(result.size() - start));
     298    }
     299}
     300
     301template<class T>
     302size_t
     303append_packed_uint_preserving_sort(char * buf, size_t buflen, T value)
     304{
     305    // Check unsigned
     306    STATIC_ASSERT_UNSIGNED_TYPE(T);
     307    STATIC_ASSERT(sizeof(T) >= 4);
     308
     309    if (value <= 0xffff) {
     310        if (value < 0xff) {
     311            if (buflen < 2)
     312                throw Xapian::InvalidArgumentError("Buffer not long enough to hold 2 byte varint.");
     313            buf[0] = char(1);
     314            buf[1] = char(value & 0xff);
     315            return 2;
     316        } else {
     317            if (buflen < 3)
     318                throw Xapian::InvalidArgumentError("Buffer not long enough to hold 3 byte varint.");
     319            buf[0] = char(2);
     320            buf[1] = char((value >> 8) & 0xff);
     321            buf[2] = char(value & 0xff);
     322            return 3;
     323        }
     324    }
     325    if (value <= 0xffffff) {
     326        if (buflen < 4)
     327            throw Xapian::InvalidArgumentError("Buffer not long enough to hold 4 byte varint.");
     328        buf[0] = char(3);
     329        buf[1] = char((value >> 16) & 0xff);
     330        buf[2] = char((value >> 8) & 0xff);
     331        buf[3] = char(value & 0xff);
     332        return 4;
     333    }
     334
     335    if (value <= 0xffffffff) {
     336        if (buflen < 5)
     337            throw Xapian::InvalidArgumentError("Buffer not long enough to hold 5 byte varint.");
     338        buf[0] = char(4);
     339        buf[1] = char((value >> 24) & 0xff);
     340        buf[2] = char((value >> 16) & 0xff);
     341        buf[3] = char((value >> 8) & 0xff);
     342        buf[4] = char(value & 0xff);
     343        return 5;
     344    }
     345
     346    // More than 4 bytes. Do first 4, then loop.
     347    if (buflen < 6)
     348        throw Xapian::InvalidArgumentError("Buffer not long enough to hold varint.");
     349    buf[1] = char((value >> 24) & 0xff);
     350    buf[2] = char((value >> 16) & 0xff);
     351    buf[3] = char((value >> 8) & 0xff);
     352    buf[4] = char(value & 0xff);
     353    value = value >> 16;
     354    value = value >> 16;
     355    size_t bytes = 4;
    239356    while (value != 0) {
     357        ++bytes;
     358        if (buflen < bytes + 1)
     359            throw Xapian::InvalidArgumentError("Buffer not long enough to hold varint.");
    240360        om_byte part = static_cast<om_byte>(value & 0xff);
    241361        value = value >> 8;
    242         result.insert(string::size_type(0), 1u, char(part));
     362        buf[bytes] = char(part);
    243363    }
    244     result.insert(string::size_type(0), 1u, char(result.size()));
    245     return result;
     364    buf[0] = char(bytes);
    246365}
    247366
    248367/** Unpack a unsigned integer, store in sort preserving order.
     
    343462    return value + '\0'; // Note - next byte mustn't be '\xff'...
    344463}
    345464
     465inline size_t
     466append_string_preserving_sort(char * buf, size_t buflen, string value)
     467{
     468    string::size_type i, j;
     469    Assert(buflen >= 2);
     470    char * pos = buf;
     471    // bufend is the end of the part of buf which the string goes in.
     472    const char * bufend = buf + buflen - 2;
     473    j = value.size();
     474    for (i = 0; i != j; ++i) {
     475        if (pos == bufend)
     476            throw Xapian::InvalidArgumentError("Buffer not long enough to hold string.");
     477        *pos = value[i];
     478        ++pos;
     479        if (value[i] == 0) {
     480            if (pos == bufend)
     481                throw Xapian::InvalidArgumentError("Buffer not long enough to hold string.");
     482            *pos = '\xff';
     483            ++pos;
     484        }
     485    }
     486    pos[0] = '\0';
     487    pos[1] = '\0';
     488    return (pos - buf) + 2;
     489}
     490
    346491inline bool
    347492unpack_string_preserving_sort(const char ** src,
    348493                              const char * src_end,
     
    401546inline string
    402547chert_docid_to_key(Xapian::docid did)
    403548{
    404     return pack_uint_preserving_sort(did);
     549    string result;
     550    append_packed_uint_preserving_sort(result, did);
     551    return result;
    405552}
    406553
    407554#endif /* OM_HGUARD_CHERT_UTILS_H */
  • chert_cursor.h

     
    202202         *          otherwise.
    203203         */
    204204        bool find_entry(const string &key);
     205        bool find_entry(const char * key, size_t keylen) {
     206            return find_entry(string(key, keylen));
     207        }
    205208
    206209        /// Position the cursor on the highest entry with key < @a key.
    207210        void find_entry_lt(const string &key) {
  • chert_postlist.cc

     
    3434Xapian::doccount
    3535ChertPostListTable::get_termfreq(const string & term) const
    3636{
    37     string key = make_key(term);
     37    make_key(term);
    3838    string tag;
    39     if (!get_exact_entry(key, tag)) return 0;
     39    if (!get_exact_entry(keybuf, keybuf_curlen, tag)) return 0;
    4040
    4141    Xapian::doccount termfreq;
    4242    const char * p = tag.data();
     
    4747Xapian::termcount
    4848ChertPostListTable::get_collection_freq(const string & term) const
    4949{
    50     string key = make_key(term);
     50    make_key(term);
    5151    string tag;
    52     if (!get_exact_entry(key, tag)) return 0;
     52    if (!get_exact_entry(keybuf, keybuf_curlen, tag)) return 0;
    5353
    5454    Xapian::termcount collfreq;
    5555    const char * p = tag.data();
     
    9191                            bool is_last_chunk_);
    9292
    9393        /// Append an entry to this chunk.
    94         void append(ChertTable * table, Xapian::docid did,
     94        void append(ChertPostListTable * table, Xapian::docid did,
    9595                    Xapian::termcount wdf);
    9696
    9797        /// Append a block of raw entries to this chunk.
     
    110110         *  with a different key to the original one, if for example the first
    111111         *  entry has changed.
    112112         */
    113         void flush(ChertTable *table);
     113        void flush(ChertPostListTable *table);
    114114
    115115    private:
    116116        string orig_key;
     
    314314}
    315315
    316316void
    317 PostlistChunkWriter::append(ChertTable * table, Xapian::docid did,
     317PostlistChunkWriter::append(ChertPostListTable * table,
     318                            Xapian::docid did,
    318319                            Xapian::termcount wdf)
    319320{
    320321    if (!started) {
     
    331332            is_first_chunk = false;
    332333            first_did = did;
    333334            chunk.resize(0);
    334             orig_key = ChertPostListTable::make_key(tname, first_did);
     335            table->make_key(tname, first_did);
     336            orig_key = string(table->keybuf, table->keybuf_curlen);
    335337        } else {
    336338            chunk.append(pack_uint(did - current_did - 1));
    337339        }
     
    379381}
    380382
    381383void
    382 PostlistChunkWriter::flush(ChertTable *table)
     384PostlistChunkWriter::flush(ChertPostListTable *table)
    383385{
    384386    DEBUGCALL(DB, void, "PostlistChunkWriter::flush", table);
    385387
     
    560562             * and we just have to write this one back to disk.
    561563             */
    562564            LOGLINE(DB, "PostlistChunkWriter::flush(): rewriting the first chunk, which still has items in it");
    563             string key = ChertPostListTable::make_key(tname);
    564             bool ok = table->get_exact_entry(key, tag);
     565            table->make_key(tname);
     566            bool ok = table->get_exact_entry(table->keybuf, table->keybuf_curlen, tag);
    565567            (void)ok;
    566568            Assert(ok);
    567569            Assert(!tag.empty());
     
    578580
    579581            tag += make_start_of_chunk(is_last_chunk, first_did, current_did);
    580582            tag += chunk;
    581             table->add(key, tag);
     583            table->add(string(table->keybuf, table->keybuf_curlen), tag);
    582584            return;
    583585        }
    584586
     
    609611             * Create a new tag with the correct key, and replace
    610612             * the old one.
    611613             */
    612             new_key = ChertPostListTable::make_key(tname, first_did);
     614            table->make_key(tname, first_did);
     615            new_key = string(table->keybuf, table->keybuf_curlen);
    613616            table->del(orig_key);
    614617        } else {
    615618            new_key = orig_key;
     
    661664                             const string & tname_,
    662665                             bool keep_reference)
    663666        : this_db(keep_reference ? this_db_ : NULL),
     667          pltable(&(this_db_->postlist_table)),
    664668          tname(tname_),
    665669          have_started(false),
    666670          cursor(this_db_->postlist_table.cursor_get()),
     
    668672{
    669673    DEBUGCALL(DB, void, "ChertPostList::ChertPostList",
    670674              this_db_.get() << ", " << tname_ << ", " << keep_reference);
    671     string key = ChertPostListTable::make_key(tname);
    672     int found = cursor->find_entry(key);
     675    pltable->make_key(tname);
     676    int found = cursor->find_entry(pltable->keybuf, pltable->keybuf_curlen);
    673677    if (!found) {
    674678        LOGLINE(DB, "postlist for term not found");
    675679        number_of_entries = 0;
     
    823827{
    824828    DEBUGCALL(DB, void,
    825829              "ChertPostList::move_to_chunk_containing", desired_did);
    826     (void)cursor->find_entry(ChertPostListTable::make_key(tname, desired_did));
     830    pltable->make_key(tname, desired_did);
     831    (void)cursor->find_entry(pltable->keybuf, pltable->keybuf_curlen);
    827832    Assert(!cursor->after_end());
    828833
    829834    const char * keypos = cursor->current_key.data();
     
    965970{
    966971    DEBUGCALL(DB, Xapian::docid, "ChertPostListTable::get_chunk", tname << ", " << did << ", " << adding << ", [from], [to]");
    967972    // Get chunk containing entry
    968     string key = make_key(tname, did);
     973    make_key(tname, did);
    969974
    970975    // Find the right chunk
    971976    AutoPtr<ChertCursor> cursor(cursor_get());
    972977
    973     (void)cursor->find_entry(key);
     978    (void)cursor->find_entry(keybuf, keybuf_curlen);
    974979    Assert(!cursor->after_end());
    975980
    976981    const char * keypos = cursor->current_key.data();
     
    10501055    LOGVALUE(DB, doclens.size());
    10511056    if (!doclens.empty()) {
    10521057        // Ensure there's a first chunk.
    1053         string current_key = make_key(string());
    1054         if (!key_exists(current_key)) {
     1058        make_key(string());
     1059        if (!key_exists(keybuf, keybuf_curlen)) {
    10551060            LOGLINE(DB, "Adding dummy first chunk");
    10561061            string newtag = make_start_of_first_chunk(0, 0, 0);
    10571062            newtag += make_start_of_chunk(true, 0, 0);
    1058             add(current_key, newtag);
     1063            add(string(keybuf, keybuf_curlen), newtag);
    10591064        }
    10601065
    10611066        map<Xapian::docid, Xapian::termcount>::const_iterator j;
     
    11161121            map<string, pair<Xapian::termcount_diff, Xapian::termcount_diff> >::const_iterator deltas = freq_deltas.find(tname);
    11171122            Assert(deltas != freq_deltas.end());
    11181123
    1119             string current_key = make_key(tname);
     1124            make_key(tname);
    11201125            string tag;
    1121             (void)get_exact_entry(current_key, tag);
     1126            (void)get_exact_entry(keybuf, keybuf_curlen, tag);
    11221127
    11231128            // Read start of first chunk to get termfreq and collfreq.
    11241129            const char *pos = tag.data();
     
    11451150                // posting list.
    11461151                if (islast) {
    11471152                    // Only one entry for this posting list.
    1148                     del(current_key);
     1153                    del(string(keybuf, keybuf_curlen));
    11491154                    continue;
    11501155                }
    11511156                AutoPtr<ChertCursor> cursor(cursor_get());
    1152                 bool found = cursor->find_entry(current_key);
     1157                bool found = cursor->find_entry(string(keybuf, keybuf_curlen));
    11531158                Assert(found);
    11541159                if (!found) continue; // Reduce damage!
    11551160                while (cursor->del()) {
     
    11651170            string newhdr = make_start_of_first_chunk(termfreq, collfreq, firstdid);
    11661171            newhdr += make_start_of_chunk(islast, firstdid, lastdid);
    11671172            if (pos == end) {
    1168                 add(current_key, newhdr);
     1173                add(string(keybuf, keybuf_curlen), newhdr);
    11691174            } else {
    11701175                Assert((size_t)(pos - tag.data()) <= tag.size());
    11711176                tag.replace(0, pos - tag.data(), newhdr);
    1172                 add(current_key, tag);
     1177                add(string(keybuf, keybuf_curlen), tag);
    11731178            }
    11741179        }
    11751180        map<Xapian::docid, pair<char, Xapian::termcount> >::const_iterator j;
  • chert_table.cc

     
    10131013    LOGCALL_VOID(DB, "ChertTable::form_key", key);
    10141014    kt.form_key(key);
    10151015}
     1016void ChertTable::form_key(const char * key, size_t keylen) const
     1017{
     1018    LOGCALL_VOID(DB, "ChertTable::form_key", key);
     1019    kt.form_key(key, keylen);
     1020}
    10161021
    10171022/* ChertTable::add(key, tag) adds the key/tag item to the
    10181023   B-tree, replacing any existing item with the same key.
     
    12321237}
    12331238
    12341239bool
     1240ChertTable::get_exact_entry(const char * key, size_t keylen, string & tag) const
     1241{
     1242    LOGCALL(DB, bool, "ChertTable::get_exact_entry", string(key, keylen) << ", [&tag]");
     1243    Assert(keylen != 0);
     1244
     1245    if (handle < 0) RETURN(false);
     1246
     1247    // An oversized key can't exist, so attempting to search for it should fail.
     1248    if (keylen > CHERT_BTREE_MAX_KEY_LEN) RETURN(false);
     1249
     1250    form_key(key, keylen);
     1251    if (!find(C)) RETURN(false);
     1252
     1253    (void)read_tag(C, &tag, false);
     1254    RETURN(true);
     1255}
     1256
     1257bool
    12351258ChertTable::key_exists(const string &key) const
    12361259{
    12371260    LOGCALL(DB, bool, "ChertTable::key_exists", key);
     
    12451268}
    12461269
    12471270bool
     1271ChertTable::key_exists(const char * key, size_t keylen) const
     1272{
     1273    LOGCALL(DB, bool, "ChertTable::key_exists", string(key, keylen));
     1274    Assert(keylen != 0);
     1275
     1276    // An oversized key can't exist, so attempting to search for it should fail.
     1277    if (keylen > CHERT_BTREE_MAX_KEY_LEN) RETURN(false);
     1278
     1279    form_key(key, keylen);
     1280    RETURN(find(C));
     1281}
     1282
     1283bool
    12481284ChertTable::read_tag(Cursor * C_, string *tag, bool keep_compressed) const
    12491285{
    12501286    LOGCALL(DB, bool, "ChertTable::read_tag", "C_, tag, " << keep_compressed);
  • chert_values.h

     
    3737{
    3838    std::string key("\0\xd8", 2);
    3939    key += pack_uint(slot);
    40     key += pack_uint_preserving_sort(did);
     40    append_packed_uint_preserving_sort(key, did);
    4141    return key;
    4242}
    4343