Eu estava implementando um algoritmo no Swift Beta e percebi que o desempenho era muito ruim. Depois de cavar mais fundo, percebi que um dos gargalos era algo tão simples quanto classificar arrays. A parte relevante está aqui:
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
Em C ++, uma operação semelhante leva 0,06s no meu computador.
No Python, são necessários 0,6s (sem truques, apenas y = classificado (x) para uma lista de números inteiros).
No Swift, são necessários 6s se eu o compilar com o seguinte comando:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
E são necessários 88s se eu compilar com o seguinte comando:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Os tempos no Xcode com compilações "Release" vs. "Debug" são semelhantes.
O que há de errado aqui? Eu pude entender alguma perda de desempenho em comparação com C ++, mas não uma desaceleração de 10 vezes em comparação com Python puro.
Edit: weather percebeu que mudar -O3
para -Ofast
faz com que esse código seja executado quase tão rápido quanto a versão C ++! No entanto, -Ofast
altera muito a semântica do idioma - nos meus testes, desabilitou as verificações de excesso de números inteiros e de indexação de array . Por exemplo, com -Ofast
o seguinte código Swift é executado silenciosamente sem travar (e imprime algum lixo):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
assim -Ofast
não é o que queremos; o ponto principal da Swift é que temos as redes de segurança no lugar. Obviamente, as redes de segurança têm algum impacto no desempenho, mas não devem tornar os programas 100 vezes mais lentos. Lembre-se de que o Java já verifica os limites da matriz e, em casos típicos, a desaceleração é por um fator muito menor que 2. E no Clang e no GCC, conseguimos -ftrapv
verificar a sobrecarga de números inteiros (assinados), e também não é tão lento.
Daí a pergunta: como podemos obter um desempenho razoável no Swift sem perder as redes de segurança?
Edit 2: Fiz mais alguns testes comparativos, com loops muito simples ao longo das linhas de
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(Aqui a operação xor existe apenas para que eu possa encontrar mais facilmente o loop relevante no código de montagem. Tentei escolher uma operação fácil de detectar, mas também "inofensiva", no sentido de que não deveria exigir nenhuma verificação relacionada para um número inteiro excede.)
Novamente, havia uma enorme diferença no desempenho entre -O3
e -Ofast
. Então, dei uma olhada no código do assembly:
Com
-Ofast
eu recebo praticamente o que eu esperaria. A parte relevante é um loop com 5 instruções em linguagem de máquina.Com
-O3
eu recebo algo que estava além da minha imaginação mais louca. O loop interno abrange 88 linhas de código de montagem. Não tentei entender tudo, mas as partes mais suspeitas são 13 invocações de "callq _swift_retain" e outras 13 invocações de "callq _swift_release". Ou seja, 26 chamadas de sub-rotina no loop interno !
Edit 3: Nos comentários, Ferruccio solicitou benchmarks justos no sentido de que eles não dependem de funções internas (por exemplo, classificação). Eu acho que o seguinte programa é um bom exemplo:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
Não há aritmética, portanto, não precisamos nos preocupar com estouros de número inteiro. A única coisa que fazemos é apenas muitas referências de matriz. E os resultados estão aqui - Swift -O3 perde quase um fator de 500 em comparação com -Ofast:
- C ++ -O3: 0,05 s
- C ++ -0: 0,4 s
- Java: 0,2 s
- Python com PyPy: 0,5 s
- Python: 12 s
- Swift -Ofast: 0,05 s
- Swift -O3: 23 s
- Swift -O0: 443 s
(Se você estiver preocupado com o fato de o compilador otimizar totalmente os loops inúteis, você pode alterá-lo para x[i] ^= x[j]
, por exemplo , e adicionar uma declaração de impressão que seja impressa x[0]
. Isso não muda nada; os tempos serão muito semelhantes.)
E sim, aqui a implementação do Python era uma implementação pura e estúpida do Python, com uma lista de entradas e aninhadas para loops. Deve ser muito mais lento que o Swift não otimizado. Algo parece estar seriamente quebrado com a Swift e a indexação de array.
Edição 4: esses problemas (assim como outros problemas de desempenho) parecem ter sido corrigidos no Xcode 6 beta 5.
Para a classificação, agora tenho os seguintes horários:
- clang ++ -O3: 0,06 s
- swiftc -Ofast: 0,1 s
- swiftc -O: 0,1 s
- swiftc: 4 s
Para loops aninhados:
- clang ++ -O3: 0,06 s
- swiftc -Ofast: 0,3 s
- swiftc -O: 0,4 s
- swiftc: 540 s
Parece que não há mais motivo para usar o inseguro -Ofast
(aka -Ounchecked
); plain -O
produz um código igualmente bom.
xcrun --sdk macosx swift -O3
. É mais curto.