Por que meu código fica mais lento quando removo verificações de limites?

Estou escrevendo uma biblioteca de álgebra linear em Rust.

Eu tenho uma função para obter uma referência a uma célula de matriz em uma determinada linha e coluna. Essa função começa com um par de asserções de que a linha e a coluna estão dentro dos limites:

#[inline(always)]
pub fn get(&self, row: usize, col: usize) -> &T {
    assert!(col < self.num_cols.as_nat());
    assert!(row < self.num_rows.as_nat());
    unsafe {
        self.get_unchecked(row, col)
    }
}

Em laços apertados, pensei que poderia ser mais rápido ignorar a verificação de limites, por isso forneço umaget_unchecked método:

#[inline(always)]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> &T {
    self.data.get_unchecked(self.row_col_index(row, col))
}

O mais estranho é que, quando eu uso esses métodos para implementar a multiplicação de matrizes (por meio de iteradores de linha e coluna), meus benchmarks mostram que na verdade é 33% mais rápido quando euVerifica os limites. Por que isso está acontecendo?

Eu tentei isso em dois computadores diferentes, um executando Linux e outro OSX, e ambos mostram o efeito.

O código completo éno github. O arquivo relevante élib.rs. Funções de interesse são:

get na linha 68get_unchecked na linha 81next na linha 551mul na linha 796matrix_mul (referência) na linha 1038

Observe que estou usando números de nível de tipo para parametrizar minhas matrizes (com a opção de tamanhos dinâmicos também por meio de tipos com tags simuladas), portanto, o benchmark está multiplicando duas matrizes 100x100.

ATUALIZAR:

Simplifiquei significativamente o código, removendo itens não usados diretamente no benchmark e removendo parâmetros genéricos. Também escrevi uma implementação de multiplicação sem usar iteradores, e essa versãonão causa o mesmo efeito. Vejoaqui para esta versão do código. Clonando ominimal-performance ramificação e execuçãocargo bench fará o benchmark das duas implementações diferentes de multiplicação (observe que as afirmações são comentadas para começar nesse ramo).

Também digno de nota é que, se eu mudar oget* para retornar cópias dos dados em vez de referências (f64 ao invés de&f64), o efeito desaparece (mas o código é um pouco mais lento o tempo todo).

questionAnswers(1)

yourAnswerToTheQuestion