mirror of
https://git.savannah.gnu.org/git/gperf.git
synced 2025-12-02 13:09:22 +00:00
Optimize: Use an array-list instead of a linked-list of keywords.
Storing list elements in contiguous memory means: less cache misses. This reduces the execution time of gperf on large inputs by ca. 30%. * src/arraylist.h: New file. * src/arraylist.cc: New file. * src/Makefile.in (OBJECTS): Add arraylist.$(OBJEXT). (ARRAYLIST_H): New variable. (arraylist.$(OBJEXT)): New rule. (search.$(OBJEXT)): Update dependencies. (SOURCE_FILES): Add arraylist.cc and arraylist.h. * src/search.cc: Include arraylist.h. (struct EquivalenceClass): An an ArrayList field. Remove the linked-list fields. Add a constructor. (Search::compute_partition, delete_partition): Update. (Search::count_possible_collisions, Search::unchanged_partition, Search::find_asso_values): Update.
This commit is contained in:
@@ -61,8 +61,19 @@ SHELL = /bin/sh
|
||||
|
||||
VPATH = $(srcdir)
|
||||
|
||||
OBJECTS = version.$(OBJEXT) positions.$(OBJEXT) options.$(OBJEXT) keyword.$(OBJEXT) keyword-list.$(OBJEXT) \
|
||||
input.$(OBJEXT) bool-array.$(OBJEXT) hash-table.$(OBJEXT) search.$(OBJEXT) output.$(OBJEXT) main.$(OBJEXT)
|
||||
OBJECTS = \
|
||||
version.$(OBJEXT) \
|
||||
positions.$(OBJEXT) \
|
||||
options.$(OBJEXT) \
|
||||
keyword.$(OBJEXT) \
|
||||
keyword-list.$(OBJEXT) \
|
||||
input.$(OBJEXT) \
|
||||
arraylist.$(OBJEXT) \
|
||||
bool-array.$(OBJEXT) \
|
||||
hash-table.$(OBJEXT) \
|
||||
search.$(OBJEXT) \
|
||||
output.$(OBJEXT) \
|
||||
main.$(OBJEXT)
|
||||
LIBS = ../lib/libgp.a @GPERF_LIBM@
|
||||
CPPFLAGS = @CPPFLAGS@ \
|
||||
-I. -I$(srcdir) \
|
||||
@@ -92,6 +103,7 @@ OPTIONS_H = options.h options.icc $(POSITIONS_H)
|
||||
KEYWORD_H = keyword.h keyword.icc
|
||||
KEYWORD_LIST_H = keyword-list.h keyword-list.icc $(KEYWORD_H)
|
||||
INPUT_H = input.h $(KEYWORD_LIST_H)
|
||||
ARRAYLIST_H = arraylist.h
|
||||
BOOL_ARRAY_H = bool-array.h bool-array.icc $(OPTIONS_H)
|
||||
HASH_TABLE_H = hash-table.h $(KEYWORD_H)
|
||||
SEARCH_H = search.h $(KEYWORD_LIST_H) $(POSITIONS_H) $(BOOL_ARRAY_H)
|
||||
@@ -109,11 +121,13 @@ keyword-list.$(OBJEXT): keyword-list.cc $(CONFIG_H) $(KEYWORD_LIST_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/keyword-list.cc
|
||||
input.$(OBJEXT): input.cc $(CONFIG_H) $(INPUT_H) $(OPTIONS_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/input.cc
|
||||
arraylist.$(OBJEXT): arraylist.cc $(CONFIG_H) $(ARRAYLIST_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/arraylist.cc
|
||||
bool-array.$(OBJEXT): bool-array.cc $(CONFIG_H) $(BOOL_ARRAY_H) $(OPTIONS_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/bool-array.cc
|
||||
hash-table.$(OBJEXT): hash-table.cc $(CONFIG_H) $(HASH_TABLE_H) $(OPTIONS_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/hash-table.cc
|
||||
search.$(OBJEXT): search.cc $(CONFIG_H) $(SEARCH_H) $(OPTIONS_H) $(HASH_TABLE_H)
|
||||
search.$(OBJEXT): search.cc $(CONFIG_H) $(SEARCH_H) $(OPTIONS_H) $(HASH_TABLE_H) $(ARRAYLIST_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/search.cc
|
||||
output.$(OBJEXT): output.cc $(CONFIG_H) $(OUTPUT_H) $(OPTIONS_H) $(VERSION_H)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(srcdir)/output.cc
|
||||
@@ -151,6 +165,7 @@ SOURCE_FILES = \
|
||||
keyword.cc $(KEYWORD_H) \
|
||||
keyword-list.cc $(KEYWORD_LIST_H) \
|
||||
input.cc $(INPUT_H) \
|
||||
arraylist.cc $(ARRAYLIST_H) \
|
||||
bool-array.cc $(BOOL_ARRAY_H) \
|
||||
hash-table.cc $(HASH_TABLE_H) \
|
||||
search.cc $(SEARCH_H) \
|
||||
|
||||
42
src/arraylist.cc
Normal file
42
src/arraylist.cc
Normal file
@@ -0,0 +1,42 @@
|
||||
/* This may look like C code, but it is really -*- C++ -*- */
|
||||
|
||||
/* Array-list container.
|
||||
|
||||
Copyright (C) 2025 Free Software Foundation, Inc.
|
||||
Written by Bruno Haible <bruno@clisp.org>.
|
||||
|
||||
This file is part of GNU GPERF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
/* Specification. */
|
||||
#include "arraylist.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
void ArrayListRepresentation::ensure_capacity (size_t n, size_t size_of_T)
|
||||
{
|
||||
size_t new_max = 2 * _nitems_max + 1;
|
||||
if (new_max < n)
|
||||
new_max = n;
|
||||
void *new_item = realloc (_item, new_max * size_of_T);
|
||||
if (new_item == NULL)
|
||||
throw std::bad_alloc();
|
||||
/* The realloc() call has moved the elements from the old storage to the
|
||||
new storage. The old storage is thus now considered uninitialized. */
|
||||
_item = new_item;
|
||||
_nitems_max = new_max;
|
||||
}
|
||||
168
src/arraylist.h
Normal file
168
src/arraylist.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/* This may look like C code, but it is really -*- C++ -*- */
|
||||
|
||||
/* Array-list container.
|
||||
|
||||
Copyright (C) 2025 Free Software Foundation, Inc.
|
||||
Written by Bruno Haible <bruno@clisp.org>.
|
||||
|
||||
This file is part of GNU GPERF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef arraylist_h
|
||||
#define arraylist_h 1
|
||||
|
||||
#include <stdlib.h> /* defines size_t, malloc(), realloc(), free(), abort() */
|
||||
#include <new>
|
||||
#if __cplusplus >= 201103L
|
||||
# include <type_traits>
|
||||
#endif
|
||||
|
||||
/* ArrayList<T> is a list of elements of type T, stored contiguously in memory
|
||||
(in order to make good use of memory caches in a CPU).
|
||||
T must be a type whose contents may be freely moved in memory (i.e. without
|
||||
fields that contain backpointers to the element itself).
|
||||
It is like std::vector<T>, but we don't want the bloated C++ standard
|
||||
library.
|
||||
It is like gnulib's "gl_list.hh" with the GL_ARRAY_LIST implementation.
|
||||
But here we don't want polymorphic containers (that force function calls at
|
||||
runtime); instead we want optimal inlining. */
|
||||
|
||||
/* In order to avoid the need for explicit instantiation for each possible
|
||||
template parameter, we make the class ArrayList<T> entirely inline.
|
||||
The class ArrayListRepresentation is used to attach methods which we want
|
||||
to be compiled only once, avoiding duplicated code for each possible
|
||||
template parameter. */
|
||||
|
||||
class ArrayListRepresentation
|
||||
{
|
||||
template <class T>
|
||||
friend class ArrayList;
|
||||
public:
|
||||
// ------------------------------ Constructors ------------------------------
|
||||
|
||||
ArrayListRepresentation ()
|
||||
{
|
||||
_item = NULL;
|
||||
_nitems = 0;
|
||||
_nitems_max = 0;
|
||||
}
|
||||
|
||||
// ------------------------------ Private stuff ------------------------------
|
||||
|
||||
private:
|
||||
/* Vector of entries. */
|
||||
void * _item;
|
||||
/* Number of elements of the vector that are used. */
|
||||
size_t _nitems;
|
||||
/* Number of elements of the vector that can be used at most. */
|
||||
size_t _nitems_max;
|
||||
|
||||
/* Ensures that _nitems_max >= n.
|
||||
To be called only when _nitems_max < n. */
|
||||
void ensure_capacity (size_t n, size_t size_of_T);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ArrayList
|
||||
{
|
||||
public:
|
||||
// ----------------------------- Constructors -----------------------------
|
||||
|
||||
/* Creates a new ArrayList<T> with 0 elements,
|
||||
with no storage allocated initially. */
|
||||
ArrayList ()
|
||||
: _rep () {}
|
||||
|
||||
// ------------------------------ Destructor ------------------------------
|
||||
|
||||
~ArrayList ()
|
||||
{
|
||||
if (_rep._item != NULL)
|
||||
{
|
||||
/* Destruct the elements (in the opposite order of their
|
||||
initialization). */
|
||||
#if __cplusplus >= 201103L
|
||||
/* See <https://en.cppreference.com/w/cpp/types/is_destructible>. */
|
||||
if (! std::is_trivially_destructible<T>::value)
|
||||
#endif
|
||||
{
|
||||
size_t index = _rep._nitems;
|
||||
while (index > 0)
|
||||
{
|
||||
--index;
|
||||
(static_cast<T *>(_rep._item))[index].~T ();
|
||||
}
|
||||
}
|
||||
/* Free the storage. */
|
||||
free (_rep._item);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Read-only member functions ----------------------
|
||||
|
||||
/* Returns the current number of elements in the list. */
|
||||
size_t size () const
|
||||
{
|
||||
return _rep._nitems;
|
||||
}
|
||||
|
||||
T& get_at (size_t index) const
|
||||
{
|
||||
#if !__OPTIMIZE__
|
||||
if (index >= _rep._nitems)
|
||||
/* index out of range. */
|
||||
abort ();
|
||||
#endif
|
||||
return (static_cast<T *>(_rep._item))[index];
|
||||
}
|
||||
|
||||
// ---------------------- Modifying member functions ----------------------
|
||||
|
||||
void set_at (size_t index, const T& value)
|
||||
{
|
||||
#if !__OPTIMIZE__
|
||||
if (index >= _rep._nitems)
|
||||
/* index out of range. */
|
||||
abort ();
|
||||
#endif
|
||||
(static_cast<T *>(_rep._item))[index] = value;
|
||||
}
|
||||
|
||||
size_t add_last (const T& value)
|
||||
{
|
||||
if (_rep._nitems == _rep._nitems_max)
|
||||
ensure_capacity (_rep._nitems + 1);
|
||||
size_t index = _rep._nitems;
|
||||
new (&(static_cast<T *>(_rep._item))[index]) T (value);
|
||||
_rep._nitems++;
|
||||
return index;
|
||||
}
|
||||
|
||||
// ----------------------------- Private stuff -----------------------------
|
||||
|
||||
private:
|
||||
/* Here, the vector of entries is a 'T *', but only the first _nitems
|
||||
elements are initialized. The remaining memory is uninitialized. */
|
||||
ArrayListRepresentation _rep;
|
||||
|
||||
/* Ensures that _nitems_max >= n.
|
||||
To be called only when _nitems_max < n. */
|
||||
void ensure_capacity (size_t n)
|
||||
{
|
||||
_rep.ensure_capacity (n, sizeof (T));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "gl_map.hh"
|
||||
#include "gl_hash_map.h"
|
||||
#include "options.h"
|
||||
#include "arraylist.h"
|
||||
#include "hash-table.h"
|
||||
|
||||
/* ============================== Portability ============================== */
|
||||
@@ -948,12 +949,12 @@ Search::prepare_asso_values ()
|
||||
struct EquivalenceClass
|
||||
{
|
||||
/* The keywords in this equivalence class. */
|
||||
KeywordExt_List * _keywords;
|
||||
KeywordExt_List * _keywords_last;
|
||||
/* The number of keywords in this equivalence class. */
|
||||
unsigned int _cardinality;
|
||||
ArrayList<KeywordExt*> _keywords;
|
||||
|
||||
EquivalenceClass * _next;
|
||||
|
||||
/* Constructor. */
|
||||
EquivalenceClass () : _keywords () {}
|
||||
};
|
||||
|
||||
struct Step
|
||||
@@ -1046,9 +1047,6 @@ Search::compute_partition (bool *undetermined) const
|
||||
if (equclass == NULL)
|
||||
{
|
||||
equclass = new EquivalenceClass();
|
||||
equclass->_keywords = NULL;
|
||||
equclass->_keywords_last = NULL;
|
||||
equclass->_cardinality = 0;
|
||||
equclass->_next = NULL;
|
||||
|
||||
/* Map this keyword (and all equivalent ones that will be seen later)
|
||||
@@ -1063,13 +1061,7 @@ Search::compute_partition (bool *undetermined) const
|
||||
}
|
||||
|
||||
/* Add the keyword to the equivalence class. */
|
||||
KeywordExt_List *cons = new KeywordExt_List(keyword);
|
||||
if (equclass->_keywords)
|
||||
equclass->_keywords_last->rest() = cons;
|
||||
else
|
||||
equclass->_keywords = cons;
|
||||
equclass->_keywords_last = cons;
|
||||
equclass->_cardinality++;
|
||||
equclass->_keywords.add_last (keyword);
|
||||
}
|
||||
|
||||
return partition;
|
||||
@@ -1082,7 +1074,6 @@ delete_partition (EquivalenceClass *partition)
|
||||
{
|
||||
EquivalenceClass *equclass = partition;
|
||||
partition = equclass->_next;
|
||||
delete_list (equclass->_keywords);
|
||||
delete equclass;
|
||||
}
|
||||
}
|
||||
@@ -1104,9 +1095,10 @@ Search::count_possible_collisions (EquivalenceClass *partition, unsigned int c)
|
||||
for (unsigned int i = 0; i <= m; i++)
|
||||
split_cardinalities[i] = 0;
|
||||
|
||||
for (KeywordExt_List *temp = cls->_keywords; temp; temp = temp->rest())
|
||||
size_t cls_size = cls->_keywords.size();
|
||||
for (size_t index = 0; index < cls_size; index++)
|
||||
{
|
||||
KeywordExt *keyword = temp->first();
|
||||
KeywordExt *keyword = cls->_keywords.get_at(index);
|
||||
|
||||
unsigned int count = 0;
|
||||
for (int i = 0; i < keyword->_selchars_length; i++)
|
||||
@@ -1116,7 +1108,7 @@ Search::count_possible_collisions (EquivalenceClass *partition, unsigned int c)
|
||||
split_cardinalities[count]++;
|
||||
}
|
||||
|
||||
sum += cls->_cardinality * cls->_cardinality;
|
||||
sum += cls->_keywords.size() * cls->_keywords.size();
|
||||
for (unsigned int i = 0; i <= m; i++)
|
||||
sum -= split_cardinalities[i] * split_cardinalities[i];
|
||||
}
|
||||
@@ -1133,16 +1125,17 @@ Search::unchanged_partition (EquivalenceClass *partition, unsigned int c) const
|
||||
{
|
||||
unsigned int first_count = UINT_MAX;
|
||||
|
||||
for (KeywordExt_List *temp = cls->_keywords; temp; temp = temp->rest())
|
||||
size_t cls_size = cls->_keywords.size();
|
||||
for (size_t index = 0; index < cls_size; index++)
|
||||
{
|
||||
KeywordExt *keyword = temp->first();
|
||||
KeywordExt *keyword = cls->_keywords.get_at(index);
|
||||
|
||||
unsigned int count = 0;
|
||||
for (int i = 0; i < keyword->_selchars_length; i++)
|
||||
if (keyword->_selchars[i] == c)
|
||||
count++;
|
||||
|
||||
if (temp == cls->_keywords)
|
||||
if (index == 0)
|
||||
first_count = count;
|
||||
else if (count != first_count)
|
||||
/* c would split this equivalence class. */
|
||||
@@ -1295,9 +1288,10 @@ Search::find_asso_values ()
|
||||
for (EquivalenceClass *cls = step->_partition; cls; cls = cls->_next)
|
||||
{
|
||||
fprintf (stderr, "\n");
|
||||
for (KeywordExt_List *temp = cls->_keywords; temp; temp = temp->rest())
|
||||
size_t cls_size = cls->_keywords.size();
|
||||
for (size_t index = 0; index < cls_size; index++)
|
||||
{
|
||||
KeywordExt *keyword = temp->first();
|
||||
KeywordExt *keyword = cls->_keywords.get_at(index);
|
||||
fprintf (stderr, " %.*s\n",
|
||||
keyword->_allchars_length, keyword->_allchars);
|
||||
}
|
||||
@@ -1347,9 +1341,10 @@ Search::find_asso_values ()
|
||||
/* Iteration Number array is a win, O(1) initialization time! */
|
||||
_collision_detector->clear ();
|
||||
|
||||
for (KeywordExt_List *ptr = cls->_keywords; ptr; ptr = ptr->rest())
|
||||
size_t cls_size = cls->_keywords.size();
|
||||
for (size_t index = 0; index < cls_size; index++)
|
||||
{
|
||||
KeywordExt *keyword = ptr->first();
|
||||
KeywordExt *keyword = cls->_keywords.get_at(index);
|
||||
|
||||
/* Compute the new hash code for the keyword, leaving apart
|
||||
the yet undetermined asso_values[]. */
|
||||
|
||||
Reference in New Issue
Block a user