Então, costumamos dizer a todos os novos usuários do R que " apply
não são vetorizados, confira o Círculo de Inferno Patrick Burns R 4 ", que diz (cito):
Um reflexo comum é usar uma função na família Apply. Isso não é vetorização, é uma ocultação de loop . A função de aplicação possui um loop for em sua definição. A função lapply enterra o loop, mas os tempos de execução tendem a ser aproximadamente iguais a um loop for explícito.
De fato, uma rápida olhada no apply
código fonte revela o loop:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
Ok, até agora, mas uma olhada lapply
ou vapply
realmente revela uma imagem completamente diferente:
lapply
## function (X, FUN, ...)
## {
## FUN <- match.fun(FUN)
## if (!is.vector(X) || is.object(X))
## X <- as.list(X)
## .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>
Então, aparentemente, não há um for
loop R escondido lá, eles estão chamando a função C escrita interna.
Uma rápida olhada na toca do coelho revela praticamente a mesma imagem
Além disso, vamos pegar a colMeans
função, por exemplo, que nunca foi acusada de não ser vetorizada
colMeans
# function (x, na.rm = FALSE, dims = 1L)
# {
# if (is.data.frame(x))
# x <- as.matrix(x)
# if (!is.array(x) || length(dn <- dim(x)) < 2L)
# stop("'x' must be an array of at least two dimensions")
# if (dims < 1L || dims > length(dn) - 1L)
# stop("invalid 'dims'")
# n <- prod(dn[1L:dims])
# dn <- dn[-(1L:dims)]
# z <- if (is.complex(x))
# .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
# .Internal(colMeans(Im(x), n, prod(dn), na.rm))
# else .Internal(colMeans(x, n, prod(dn), na.rm))
# if (length(dn) > 1L) {
# dim(z) <- dn
# dimnames(z) <- dimnames(x)[-(1L:dims)]
# }
# else names(z) <- dimnames(x)[[dims + 1]]
# z
# }
# <bytecode: 0x0000000008f89d20>
# <environment: namespace:base>
Hã? Também chama apenas o .Internal(colMeans(...
que também podemos encontrar na toca do coelho . Então, como isso é diferente .Internal(lapply(..
?
Na verdade, um benchmark rápido revela que o sapply
desempenho não é pior colMeans
e muito melhor do que um for
loop para um grande conjunto de dados
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user system elapsed
# 1.69 0.03 1.73
system.time(sapply(m, mean))
# user system elapsed
# 1.50 0.03 1.60
system.time(apply(m, 2, mean))
# user system elapsed
# 3.84 0.03 3.90
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user system elapsed
# 13.78 0.01 13.93
Em outras palavras, não é correto dizer que lapply
e vapply
são realmente vectorized (em comparação com apply
o que é um for
loop que também chama lapply
) eo que Patrick Burns realmente quer dizer?
fonte
*apply
funções chamam repetidamente funções R, o que as torna repetidas. Com relação ao bom desempenho desapply(m, mean)
: Possivelmente o código C dolapply
método expede apenas uma vez e depois chama o método repetidamente?mean.default
é bastante otimizado.Respostas:
Primeiro de tudo, no seu exemplo, você faz testes em um "data.frame" que não é justo e
colMeans
, como eles têm uma sobrecarga:apply
"[.data.frame"
Em uma matriz, a imagem é um pouco diferente:
Analisando a parte principal da questão, a principal diferença entre
lapply
/mapply
/ etc e R-loops diretos é onde o loop é feito. Como observa Roland, os loops C e R precisam avaliar uma função R em cada iteração que é a mais cara. As funções C realmente rápidas são aquelas que fazem tudo em C; então, acho que deve ser isso o que "vetorizado" significa?Um exemplo em que encontramos a média em cada um dos elementos de uma "lista":
( EDIT 11 de maio de 16 : acredito que o exemplo de encontrar a "média" não é uma boa configuração para as diferenças entre avaliar iterativamente uma função R e o código compilado, (1) devido à particularidade do algoritmo médio de R em "numérico" s sobre um simples
sum(x) / length(x)
e (2) deve fazer mais sentido testar em "list" s comlength(x) >> lengths(x)
. Portanto, o exemplo "mean" é movido para o final e substituído por outro.)Como um exemplo simples, podemos considerar a descoberta do oposto de cada
length == 1
elemento de uma "lista":Em um
tmp.c
arquivo:E no lado R:
com dados:
Avaliação comparativa:
(Segue o exemplo original da descoberta média):
fonte
all_C
eC_and_R
funções. Eu também encontrado nas documentações decompiler::cmpfun
uma versão R antiga de lapply que contém um R realfor
loop, eu estou começando a suspeitar que Burns, estava se referindo a que versão antiga que foi vectorized desde então, e esta é a resposta real para a minha pergunta .. ..la1
de?compiler::cmpfun
parece, ainda assim, produzir a mesma eficiência com todas asall_C
funções, exceto as. Eu acho que, de fato, torna-se uma questão de definição; "vetorizado" significa qualquer função que aceite não apenas escalares, alguma função que tenha código C, alguma função que utilize cálculos apenas em C?lapply
não é vetorizado simplesmente porque está avaliando uma função R em cada iteração no código C?Para mim, vetorização é principalmente tornar seu código mais fácil de escrever e mais fácil de entender.
O objetivo de uma função vetorizada é eliminar a contabilidade associada a um loop for. Por exemplo, em vez de:
Você pode escrever:
Isso facilita ver o que é o mesmo (os dados de entrada) e o que é diferente (a função que você está aplicando).
Uma vantagem secundária da vetorização é que o loop for geralmente é escrito em C, e não em R. Isso traz benefícios substanciais de desempenho, mas não acho que seja a propriedade chave da vetorização. A vetorização é fundamental para salvar seu cérebro, não para salvar o trabalho do computador.
fonte
for
loops C e R. OK, um loop C pode ser otimizado pelo compilador, mas o principal ponto de desempenho é se o conteúdo do loop é eficiente. E, obviamente, o código compilado geralmente é mais rápido que o código interpretado. Mas é provavelmente o que você quis dizer.Concordo com a opinião de Patrick Burns de que é um pouco oculto em loop e não uma vetorização de código . Aqui está o porquê:
Considere este
C
trecho de código:O que gostaríamos de fazer é bastante claro. Mas como a tarefa é executada ou como ela pode ser executada não é realmente. Um loop for, por padrão, é uma construção serial. Não informa se ou como as coisas podem ser feitas em paralelo.
A maneira mais óbvia é que o código seja executado de maneira seqüencial . Carregue
a[i]
e ligue osb[i]
registros, adicione-os, armazene o resultadoc[i]
e faça isso para cada umi
.No entanto, os processadores modernos têm um conjunto de instruções de vetor ou SIMD que é capaz de operar em um vetor de dados durante a mesma instrução ao executar a mesma operação (por exemplo, adicionando dois vetores como mostrado acima). Dependendo do processador / arquitetura, pode ser possível adicionar, digamos, quatro números de
a
eb
sob a mesma instrução, em vez de um de cada vez.Seria ótimo se o compilador identificasse esses blocos de código e os vetorizasse automaticamente , o que é uma tarefa difícil. A vetorização automática de código é um tópico de pesquisa desafiador em Ciência da Computação. Mas com o tempo, os compiladores ficaram melhores nisso. Você pode verificar os recursos de vetorização automática
GNU-gcc
aqui . Da mesma forma porLLVM-clang
aqui . E você também pode encontrar alguns benchmarks no último link em comparação comgcc
eICC
(compilador Intel C ++).gcc
(Estou ativov4.9
), por exemplo, não vetoriza código automaticamente na-O2
otimização de nível. Portanto, se executássemos o código mostrado acima, ele seria executado sequencialmente. Aqui está o momento para adicionar dois vetores inteiros de comprimento 500 milhões.Precisamos adicionar a sinalização
-ftree-vectorize
ou alterar a otimização para o nível-O3
. (Observe que também-O3
realiza outras otimizações adicionais ). O sinalizador-fopt-info-vec
é útil, pois informa quando um loop foi vetorizado com êxito).Isso nos diz que a função é vetorizada. Aqui estão os horários que comparam versões não vetorizadas e vetorizadas em vetores inteiros de comprimento 500 milhões:
Esta parte pode ser pulada com segurança sem perder a continuidade.
Os compiladores nem sempre têm informações suficientes para vetorizar. Poderíamos usar a especificação OpenMP para programação paralela , que também fornece uma diretiva compilador simd para instruir os compiladores a vetorizar o código. É essencial garantir que não haja sobreposição de memória, condições de corrida, etc. ao vetorizar o código manualmente, caso contrário, resultará em resultados incorretos.
Ao fazer isso, solicitamos especificamente ao compilador que o vetorize, não importa o quê. Precisamos ativar as extensões OpenMP usando o sinalizador de tempo de compilação
-fopenmp
. Ao fazer isso:o que é ótimo! Isso foi testado com o gcc v6.2.0 e o llvm clang v3.9.0 (ambos instalados via homebrew, MacOS 10.12.3), ambos compatíveis com o OpenMP 4.0.
Nesse sentido, mesmo que a página da Wikipedia sobre Programação de matrizes mencione que os idiomas que operam em matrizes inteiras geralmente chamam isso de operações vetorizadas , na verdade, ele está ocultando o IMO (a menos que seja realmente vetorizado).
No caso de R, par
rowSums()
oucolSums()
código em C não explora a vetorização de código IIUC; é apenas um loop em C. O mesmo vale paralapply()
. No caso deapply()
, é em R. Todos esses são, portanto, ocultos .HTH
Referências:
fonte
Então, para resumir as ótimas respostas / comentários em algumas respostas gerais e fornecer alguns antecedentes: R tem 4 tipos de loops ( da ordem não vetorizada à vetorizada )
for
Loop R que chama repetidamente as funções R em cada iteração ( Não vetorizado )Então a
*apply
família é o segundo tipo. Excetoapply
qual é mais do primeiro tipoVocê pode entender isso a partir do comentário em seu código-fonte
Isso significa que
lapply
o código s aceita uma função não avaliada de R e depois a avalia dentro do próprio código C. Esta é basicamente a diferença entrelapply
a.Internal
chamada sQue possui um
FUN
argumento que contém uma função RE a
colMeans
.Internal
chamada que não temFUN
argumentocolMeans
, diferente delapply
saber exatamente qual função ele precisa usar, calcula a média internamente no código C.Você pode ver claramente o processo de avaliação da função R em cada iteração no
lapply
código CPara resumir,
lapply
não é vetorizado , embora tenha duas vantagens possíveis sobre ofor
loop R simplesAcessar e atribuir em um loop parece ser mais rápido em C (ou seja, em
lapply
uma função) Embora a diferença pareça grande, ainda permanecemos no nível de microssegundos e o caro é a avaliação de uma função R em cada iteração. Um exemplo simples:Como mencionado por @Roland, ele executa um loop C compilado, em vez de um loop R interpretado
Embora ao vetorizar seu código, há algumas coisas que você precisa levar em consideração.
df
) é de classedata.frame
, algumas funções vectorized (tais comocolMeans
,colSums
,rowSums
, etc.) terá de convertê-lo em uma matriz em primeiro lugar, simplesmente porque esta é a forma como eles foram concebidos. Isso significa que, para grandes empresas,df
isso pode criar uma enorme sobrecarga. Emboralapply
não precise fazer isso, pois extrai os vetores reais dedf
(comodata.frame
é apenas uma lista de vetores) e, portanto, se você não tiver muitas colunas, mas muitas linhas,lapply(df, mean)
às vezes pode ser uma opção melhor do quecolMeans(df)
..Primitive
, e genérico (S3
,S4
) , veja aqui para obter mais informações. A função genérica precisa executar um despacho de método que às vezes é uma operação cara. Por exemplo,mean
é genéricoS3
função whilesum
éPrimitive
. Assim, algumas vezeslapply(df, sum)
pode ser muito eficiente em comparaçãocolSums
com os motivos listados acimafonte
colMeans
etc, que são construídos para tratar apenas matrizes. (2) Estou um pouco confuso com sua terceira categoria; Não sei dizer ao que você está se referindo. (3) Como você está se referindo especificamentelapply
, acredito que não faz diferença entre"[<-"
R e C; ambos pré-alocam uma "lista" (um SEXP) e a preenchem em cada iteração (SET_VECTOR_ELT
em C), a menos que eu esteja perdendo o seu ponto.do.call
que ele constrói uma chamada de função no ambiente C e apenas a avalia; embora eu esteja tendo dificuldades para compará-lo a loop ou vetorização, pois faz algo diferente. Na verdade, você está certo em acessar e atribuir diferenças entre C e R, embora ambos permaneçam no nível de microssegundos e não afetem enormemente o resultado, uma vez que o custo é a chamada de função R iterativa (compareR_loop
eR_lapply
na minha resposta ) (Eu vou editar o seu post com uma referência, eu espero que você, ainda assim, não vai se importar)Vectorize()
como exemplo) também o usam no sentido da interface do usuário. Penso que grande parte da discordância neste tópico é causada pelo uso de um termo para dois conceitos separados, mas relacionados.