A escolha da linguagem de programação adequada para determinadas tarefas pode impactar significativamente o desempenho e a eficiência de uma aplicação. Com o amadurecimento de linguagens modernas, como Julia, que prometem alta performance e facilidade de uso, é natural que comparações com linguagens já consolidadas, como Python, sejam realizadas.
Neste contexto, a análise de desempenho entre linguagens torna-se essencial para entender suas capacidades e limitações em cenários específicos. Para o teste comparativo de velocidade entre as linguagens Python e Julia, foi utilizado a fórmula para calcular o 1º e 2º coeficientes de Assimetria de Pearson, sem o uso de bibliotecas em ambas as linguagens.
Essa abordagem permite avaliar não apenas a eficiência computacional, mas também a clareza e a simplicidade da implementação direta de algoritmos matemáticos em cada uma delas.
O Teste comparativo
Foram gerados dois vetores (listas), um contendo uma quantidade pré-definida de valores entre um intervalo (entre 1 a 1.000.000.000) e o outro contendo as frequências, o número de repetição desses valores, conforme o exemplo abaixo.
Valores | Frequências |
---|---|
30 | 2 |
64 | 1 |
97 | 4 |
12 | 5 |
26 | 3 |
Total | 15 |
A tabela ao lado mostra um conjunto de dados, composto por 5 elementos contendo os valores e suas frequências, sendo:
- 2 elementos 30
- 1 elemento 64
- 4 elementos 97
- 5 elementos 12
- 3 elementos 26
$$Dados\ =\ \begin{bmatrix} 30\ \ 30\ \ 64\ \ 97\ \ 97 \\ 97\ \ 97\ \ 12\ \ 12\ \ 12 \\ 12\ \ 12\ \ 26\ \ 26\ \ 26\end{bmatrix}$$
O vetor contendo os valores foi gerado aleatoriamente de acordo com o número de elementos pré-definidos.
As frequências seguiram a lista $[27, 31, 34, 25, 35, 32, 28, 33, 29, 26]$, que contém 10 elementos, e somam o valor total de 300, e, de forma repetitiva, foram duplicados até que se chegou ao número pré-definido, conforme a tabela a seguir:
Testes | Número de elementos diferentes | Total de elementos gerados |
---|---|---|
Teste 1 | 50 | 1.500 |
Teste 2 | 100 | 3.000 |
Teste 3 | 500 | 15.000 |
Teste 4 | 1.000 | 30.000 |
Teste 5 | 10.000 | 300.000 |
Teste 6 | 50.000 | 1.500.000 |
Teste 7 | 100.000 | 3.000.000 |
Teste 8 | 200.000 | 6.000.000 |
Teste 9 | 500.000 | 15.000.000 |
Teste 10 | 1.000.000 | 30.000.000 |
Teste 11 | 10.000.000 | 300.000.000 |
Teste 12 | 50.000.000 | 1.500.000.000 |
Teste 13 | 100.000.000 | 3.000.000.000 |
A escolha por gerar dois vetores com valores e frequências separados se dá pelo alto custo computacional que seria gerar um único vetor com todas as repetições.
Cada conjunto de dados foi testado 5 vezes com a fórmula da assimetria, e dos 5 resultados de tempo obtidos, foi retirado uma média. Portanto, cada resultado é a média dos 5 testes.
A escolha por gerar dois vetores com valores e frequências separados se dá pelo alto custo computacional que seria gerar um único vetor com todas as repetições.
Cada conjunto de dados foi testado 5 vezes com a fórmula da assimetria, e dos 5 resultados de tempo obtidos, foi retirado uma média. Portanto, cada resultado do teste comparativo é a média dos 5 testes.
Códigos utilizados
Pythonfrom decimal import Decimal
from pint import UnitRegistry
import math
def mediana(valor, freq):
# Ordena os valores e suas frequências correspondentes
valores, frequencias = zip(*sorted(zip(valor, freq)))
valores = list(valores)
frequencias = list(frequencias)
# Calcula a soma total das frequências
total = sum(frequencias)
# Determina o ponto médio
meio = total / 2
# Calcula as frequências acumuladas
acumulado = 0
for i, freq in enumerate(frequencias):
acumulado += freq
# Caso ímpar: encontra o valor central
if total % 2 != 0 and acumulado >= meio:
return valores[i]
# Caso par: encontra os dois valores centrais
if total % 2 == 0:
if acumulado > meio:
return valores[i]
elif acumulado == meio:
return (valores[i] + valores[i + 1]) / 2
def assimetria(valor, freq, soma_freq):
from datetime import datetime
t1 = datetime.now() # Medindo o tempo inicial
# Descobrindo o valor da média
m = sum(map(lambda x, y: x * y, valor, freq)) / soma_freq
# Descobrindo o valor da moda
Moda = valor[freq.index(max(freq))]
# Descobrindo o valor da mediana
Med = mediana(valor, freq)
# Descobrindo o valor do desvio padrão
s = (sum(map(lambda x, y: (((x-m)**2)*y) / soma_freq, valor, freq))) ** 0.5
# Calculando os coeficientes de assimetria
prim = (m - Moda) / s
seg = 3 * (m - Med) / s
t2 = datetime.now() # Medindo o tempo final
diff = t2 - t1 # Diferença de tempo
return diff.total_seconds()
def teste_velocidade(amostra):
lista = []
soma_freq = ""
for n in range(5):
import random
# Gerando o vetor com os valores, sem repetição
valor = random.sample(range(1, 1_000_000_001), amostra)
# Gerando o vetor contendo as frequências
sequencia = [27, 31, 34, 25, 35, 32, 28, 33, 29, 26]
freq = (sequencia * math.ceil(amostra / len(sequencia)))[:amostra]
soma_freq = sum(freq)
# Chamando a função e imprimindo os resultados
tempo_total = assimetria(valor, freq, soma_freq)
lista.append(tempo_total)
media = sum(lista) / len(lista)
num_form = ""
if str(media).count("e") > 0:
num_form = f"{Decimal(media):.10f}"
else:
num_form = f"{media:.10f}"
if int(num_form.split(".")[0]) > 0:
resultado = f"Tempo em segundos: {media:.2f} s"
else:
def contagem_de_zeros(num:float) -> float:
num_str = f"{Decimal(num):.10f}"
decimal_part = num_str.split(".")[1]
leading_zeros = []
for char in decimal_part:
if char == '0':
leading_zeros.append(int(char))
else:
break
return len(leading_zeros)
contagem = contagem_de_zeros(num_form)
# Criar um objeto de registro de unidades
ureg = UnitRegistry()
# Definir o tempo em segundos
tempo_em_segundos = float(num_form) * ureg.second
resultado = ""
# Converter para outras unidades
if contagem in list(range(0,3)):
tempo_em_milissegundos = tempo_em_segundos.to(ureg.millisecond).magnitude
resultado = f"Tempo em milissegundos: {tempo_em_milissegundos:.2f} ms"
if contagem in list(range(3,6)):
tempo_em_microssegundos = tempo_em_segundos.to(ureg.microsecond).magnitude
resultado = f"Tempo em microssegundos: {tempo_em_microssegundos:.2f} μs"
if contagem in list(range(6,11)):
tempo_em_nanosegundos = tempo_em_segundos.to(ureg.nanosecond).magnitude
resultado = f"Tempo em nanosegundos: {tempo_em_nanosegundos:.2f} ns"
return soma_freq, num_form, resultado
for amostra in [50, 100, 500, 1_000, 10_000, 50_000, 100_000, 200_000, 500_000, 1_000_000, 10_000_000, 50_000_000, 100_000_000]:
tot, num, res = teste_velocidade(amostra)
print(f"Frequência: {amostra}\nTamanho da amostra: {tot}\ntempo: {num}\n{res}\n\n")
using StatsBase
using Unitful
using Printf
function mediana(valor, freq)
# Ordena os valores e as frequências correspondentes
ordenado = sort(collect(zip(valor, freq)))
valores = [v[1] for v in ordenado]
frequencias = [v[2] for v in ordenado]
# Calcula a soma total das frequências
total = sum(frequencias)
# Determina o ponto médio
meio = total / 2
# Calcula as frequências acumuladas
acumulado = 0
for i in eachindex(frequencias)
acumulado += frequencias[i]
# Caso ímpar: encontra o valor central
if isodd(total) && acumulado >= meio
return valores[i]
end
# Caso par: encontra os dois valores centrais
if iseven(total)
if acumulado > meio
return valores[i]
elseif acumulado == meio
return (valores[i] + valores[i + 1]) / 2
end
end
end
end
function assimetria(valor, freq, soma_freq)
# Descobrindo o valor da média
m = sum(valor .* freq) / soma_freq
# Descobrindo o valor da moda
Moda = valor[argmax(freq)]
# Chamando a função para descobrir o valor da mediana
Med = mediana(valor, freq)
# Descobrindo o valor do desvio padrão
s = sum(((valor .- m).^2) .* freq / soma_freq)^0.5
# Calculando os coeficientes de assimetria
prim = (m - Moda) / s
seg = 3*(m - Med) / s
return prim, seg
end
function teste_velocidade(amostra)
valores = Vector{Float64}(undef, 6) # Pré-alocação para 6 elementos
soma_freq = ""
for n in 1:6
# Gerando o vetor com os valores, sem repetição
valor = sort(sample(0:1_000_000_000, amostra; replace=false))
# Gerando o vetor contendo as frequências
sequencia = [27, 31, 34, 25, 35, 32, 28, 33, 29, 26]
freq = repeat(sequencia, cld(amostra, length(sequencia)))[1:amostra]
soma_freq = sum(freq)
# Chamando a função assmetria e calculando o tempo gasto com @elapsed
tempo_total = @elapsed assimetria(valor, freq, soma_freq)
# Salvando o tempo gasto em no vetor valores
valores[n] = tempo_total
end
media = sum(valores[2:end]) / length(valores[2:end])
num_form = ""
if count(x -> x == 'e', string(media)) > 0
num_form = @sprintf("%.10f", parse(Float64, string(media)))
else
num_form = string(media)
end
num_seg = split(num_form, ".")
if parse(Float64, num_seg[1]) > 0
resultado = "Tempo em segundos: $(round(parse(Float64, num_form[1:5]), digits=2)) s"
else
# Código para retornar o valor formatado, em milissegundos, microssegundos ou nanossegundos
# Função para descobrir quantos zeros tem após o ponto
function contagem_de_zeros(num::Float64)
num_str = @sprintf("%.10f", parse(Float64, string(num)))
decimal_part = split(num_str, ".")[2]
# Iterar sobre os caracteres para encontrar zeros iniciais
leading_zeros = ""
for char in decimal_part
if char == '0'
leading_zeros *= char
else
break
end
end
return length(leading_zeros)
end
contagem = contagem_de_zeros(parse(Float64, num_form))
# Definir o tempo em segundos
tempo_em_segundos = parse(Float64, num_form)u"s"
resultado = ""
# Converter para outras unidades
if contagem in 0:2
tempo_em_milissegundos = uconvert(u"ms", tempo_em_segundos)
valor_formatado = @sprintf("%.2f ms", ustrip(tempo_em_milissegundos))
resultado = "Tempo em milissegundos: $valor_formatado"
end
if contagem in 3:5
tempo_em_microssegundos = uconvert(u"μs", tempo_em_segundos)
valor_formatado = @sprintf("%.2f μs", ustrip(tempo_em_microssegundos))
resultado = "Tempo em microssegundos: $valor_formatado"
end
if contagem in 6:10
tempo_em_nanosegundos = uconvert(u"ns", tempo_em_segundos)
valor_formatado = @sprintf("%.2f ns", ustrip(tempo_em_nanosegundos))
resultado = "Tempo em nanossegundos: $valor_formatado"
end
end
return soma_freq, num_form, resultado
end
for amostra in [50, 100, 500, 1_000, 10_000, 50_000, 100_000, 200_000, 500_000, 1_000_000, 10_000_000, 50_000_000, 100_000_000]
tot, num, res = teste_velocidade(amostra)
println("Frequência: $amostra\nTamanho da amostra: $tot\ntempo: $num\n$res\n\n")
end
Os dois códigos foram criados para serem os mais semelhantes possível, tendo utilizado o mínimo de bibliotecas externas para rodar e apresentar os resultados, e ambos foram pré-otimizados com as bibliotecas Profile e Cprofile.
Os códigos foram testados em um computador contendo um processador Ryzen 5700G, 32Gb de Ram e armazenamento NVMe, sem uma placa de vídeo dedicada, e com sistema operacional Linux.
Os resultados foram obtidos em segundos (seg), e convertidos, para melhor compreensão, em unidades de medidas temporais anteriores e posteriores, como descritas a seguir.
Abaixo, o exemplo de 1 segundo convertido em unidades de medida maiores e menores:
- Microssegundos (μs).. 1.000.000 μs
- Milissegundos (ms)….. 1.000 ms
- Segundos (seg)…………..1.0 seg
- Minutos (min)…………… 0,0166666667 min
Resultados
Os resultados do teste comparativo foram bem interessantes. No último experimento mesmo, enquanto Python demorou 5 minutos e 15 segundos para executar o teste, Julia executou em 1.47 segundos. É uma diferença impressionante!
O desempenho de Julia foi superior ao de Python em todos os casos analisados, embora a diferença não tenha sido exponencial. A menor variação foi observada no primeiro resultado, em que Julia foi 14.51 vezes mais rápida que Python. Já a maior variação ocorreu no último resultado, com Julia sendo 209.90 vezes mais rápida que Python.
O gráfico interativo abaixo ilustra o resultado de cada teste.
Não foram feitos mais testes com valores amostrais maiores, como 200, 500 milhões ou 1 bilhão de valores únicos, por limitações no uso da memória do computador utilizado.
Deixe um comentário