Processando grandes quantidades de dados (gigabytes) uso índices para matrizes de dados. Como o acesso aos dados pode levar à ineficiência do cache, quero armazenar em cache alguns dados do array junto com o índice, o que proporciona uma aceleração drástica para operações por meio de índices.
A quantidade de dados armazenados em cache é uma escolha em tempo de compilação que deve incluir quantidade zero de dados em cache. Tenho uma grande quantidade de índices, então neste caso não quero pagar por elementos “vazios” extras como std::array
acontece, por exemplo.
Então, fiz um template com especialização:
using index_t = unsigned int;
using lexem_t = unsigned int;
template <std::size_t t_arg_cache_line_size>
struct lexem_index_with_cache_t {
index_t index;
std::array<lexem_t, t_arg_cache_line_size> cache_line;
constexpr std::size_t cache_line_size() const {
return t_arg_cache_line_size;
}
};
template<>
struct lexem_index_with_cache_t<0> {
index_t index;
static std::array<lexem_t, 0> cache_line;
constexpr std::size_t cache_line_size() const {
return 0;
}
};
std::array<lexem_t, 0> lexem_index_with_cache_t<0>::cache_line;
O problema é esse “hack” que usei na especialização com tamanho zero que utiliza membro estático para dar acesso formal ao cache_line
while está vazio e o acesso não é realmente necessário. Isso me permite evitar especializações em funções que utilizam este modelo, como aqui:
using lexem_index_with_cache = lexem_index_with_cache_t<0>;
template <typename T>
class seq_forward_comparator_cached
{
const std::vector<T>& vec;
public:
seq_forward_comparator_cached(const std::vector<T>& vec) : vec(vec) { }
bool operator() (const lexem_index_with_cache& idx1, const lexem_index_with_cache& idx2)
{
if (idx1.index == idx2.index) {
return false;
}
const auto it1_cache_line = idx1.cache_line; // This code wouldn’t compile in absence of static “hack”
const auto it2_cache_line = idx2.cache_line; // This code wouldn’t compile in absence of static “hack”
auto res = std::lexicographical_compare_three_way(
it1_cache_line.begin(), it1_cache_line.end(),
it2_cache_line.begin(), it2_cache_line.end());
if (res == std::strong_ordering::equal) {
auto range1 = std::ranges::subrange(vec.begin() + idx1.index + idx1.cache_line_size(), vec.end());
auto range2 = std::ranges::subrange(vec.begin() + idx2.index + idx2.cache_line_size(), vec.end());
return std::ranges::lexicographical_compare(range1, range2);
}
return res == std::strong_ordering::less;
}
};
Claro, posso implementar outra especialização de modelo deste modelo para cache de tamanho zero, mas isso levará à duplicação de código e tenho muitas dessas funções, então não quero especializar todas elas.
static
Por outro lado, qual é a maneira adequada no C++ moderno de evitar esse hack e possível duplicação de código?
Não tenho certeza, talvez algum tipo de código condicional incluído dependendo do tipo possa ajudar.
Eu gostaria de evitar o acesso a cache_line
uma função, mas se esse for o único caso, dê uma pista sobre a abordagem.
O código compilável está aqui .
Adicionei um exemplo com um truque para usar o
if constexpr(...)
código que não é de modelo.Agora é impossível usar a
at()
função acidentalmente, ao contrário da solução de membro estático.