Pular para o conteúdo

A diferença é real, ou é ruído?

Mann-Whitney sobre as amostras: o teste que impede a ferramenta de inventar ganho — e por que não é o teste t.

Comparar uma ação com ela mesma não pode dar "3% mais rápida". É o que separa medição de superstição, e é o primeiro teste do arquivo de testes.

dataforge
adopt Arcane.Perfil as P

action consulta():
    yield sum(range(400))

// a MESMA acao dos dois lados: a diferenca e ruido, e a ferramenta
// tem de dizer isso
v := P.comparar(consulta, consulta, {"amostras": 40})
assert v["mais_rapido"] is "empate"
assert not v["significativo"]
dataforge
adopt Arcane.Perfil as P

action rapida():
    yield sum(range(400))

action lenta():
    yield sum(range(9000))

d := P.comparar(rapida, lenta, {"amostras": 30})
assert d["significativo"]
assert d["mais_rapido"] is "a"
assert d["fator"] bigger 2
assert d["p_valor"] smaller 0.05

Por que Mann-Whitney, e não o teste t#

Tempo de execução não é normal: tem cauda longa à direita, piso duro à esquerda (nada roda em tempo negativo) e picos de escalonamento do sistema. Um teste t supõe normalidade e responde com confiança sobre uma suposição falsa.

O U de Mann-Whitney não supõe nada sobre a forma — ele compara ordens. E a correção de empates importa quando o relógio tem resolução grossa e muitas amostras dão o mesmo valor.

CampoO que diz
p_valora chance de ver esta diferença se as duas fossem iguais
significativop_valor abaixo de alfa (padrão 0,05)
sobreposicaoo tamanho do efeito: a chance de um sorteio de A ser menor que um de B. É o que p não diz
fatorquantas vezes, na mediana — e só quando é significativo
a, ba distribuição completa de cada lado

Regressão: piorou desde a semana passada?#

Um número sozinho não responde isso. A linha de base fica num arquivo, e o conferir compara.

dataforge
adopt Arcane.Perfil as P
adopt Arcane.IO as IO
adopt Arcane.OS as OS

base := $"{OS.temp_dir()}/df-doc-{randint(100000, 999999)}.json"
P.guardar("consulta", {"p95": 1.0, "p50": 1.0, "media": 1.0}, base)

// tres vezes mais lento: regrediu
ruim := P.conferir("consulta", {"p95": 3.0, "p50": 3.0, "media": 3.0},
                   {"arquivo": base, "tolerancia": 0.2})
assert ruim["regrediu"] and ruim["fator"] is 3.0

// 5% mais lento, com 20% de tolerancia: e ruido de maquina
ok := P.conferir("consulta", {"p95": 1.05, "p50": 1.05, "media": 1.05},
                 {"arquivo": base, "tolerancia": 0.2})
assert not ok["regrediu"]

// a PRIMEIRA medida nunca reprova: sem base nao ha regressao
nova := P.conferir("nunca-medida", {"p95": 1.0}, {"arquivo": base})
assert not nova["conhecida"] and not nova["regrediu"]

IO.delete(base)