É melhor usar System.arraycopy (…) do que um loop for para copiar matrizes?

89

Quero criar uma nova matriz de objetos juntando duas matrizes menores.

Eles não podem ser nulos, mas o tamanho pode ser 0.

Não posso escolher entre essas duas maneiras: são equivalentes ou é mais eficiente (por exemplo, system.arraycopy () copia pedaços inteiros)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

ou

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

A única diferença é a aparência do código?

EDITAR: obrigado pela pergunta vinculada, mas eles parecem ter uma discussão não resolvida:

É realmente mais rápido se it is not for native types: byte [], Object [], char []? em todos os outros casos, uma verificação de tipo é executada, qual seria o meu caso e, portanto, seria equivalente ... não?

Em outra pergunta vinculada, eles dizem que the size matters a lot, para tamanho> 24, ganha system.arraycopy (), para menos de 10, manual para loop é melhor ...

Agora estou realmente confuso.

Daren
fonte
16
arraycopy()é uma chamada nativa, que certamente é mais rápida.
Sotirios Delimanolis,
4
Você já tentou comparar as duas implementações diferentes?
Alex
5
Dê uma olhada aqui: stackoverflow.com/questions/2772152/…
Sotirios Delimanolis
15
Você deve escolher o que achar mais legível e mais fácil de manter no futuro. Somente quando você determinar que esta é a fonte de um gargalo, você deve mudar sua abordagem.
arshajii
1
Não reinvente a roda!
camickr

Respostas:

87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

Eu sei que os testes JUnit não são realmente os melhores para benchmarking, mas
testHardCopyBytes levou 0,157s para ser concluído
e
testArrayCopyBytes levou 0,086s para ser concluído.

Acho que depende da máquina virtual, mas parece que ela copia blocos de memória em vez de copiar elementos de array único. Isso aumentaria absolutamente o desempenho.

EDIT:
Parece que o desempenho de System.arraycopy está em todo lugar. Quando Strings são usados ​​em vez de bytes e as matrizes são pequenas (tamanho 10), obtenho estes resultados:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

Aqui está o que parece quando os arrays estão no tamanho 0x1000000. Parece que System.arraycopy definitivamente vence com arrays maiores.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

Que peculiar!

Obrigado, Daren, por apontar que as referências são copiadas de maneira diferente. Isso tornou esse problema muito mais interessante!

Trent Small
fonte
2
Obrigado por seu esforço, mas você perdeu pontos aparentemente cruciais: tipos não nativos (crie uma classe aleatória com anythign para que o array contenha referências) e tamanho ... parece que para um tamanho de array menor, o for-loop manual é mais rápido. Importa-se de corrigir isso?
Daren
2
Nossa, você está certo! Isso é interessante. Colocar Strings nessas matrizes em vez de bytes faz uma grande diferença: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small
Que resultados você está obtendo então? também interessante seria tentar com array size = 10 ... obrigado! (Eu gostaria de ter meu IDE aqui, estou codificando sem compilador).
Daren
Bem, agora eu os envolvi em algumas chamadas System.nanoTime () e configurei size = 10 para ver quantos nanossegundos cada uma leva. Parece que para pequenos arrays primitivos, loops são melhores; para referências, arrayCopy é melhor .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Pequeno
Resultados muito interessantes! muito obrigado! você pode, por favor, editar sua resposta para incluir isso, e eu terei prazer em aceitá-la, então continua sendo a primeira para que todos vejam ... você já obteve meu voto. :)
Daren
36

Arrays.copyOf(T[], int)é mais fácil de ler. Internamente, ele usa o System.arraycopy()que é uma chamada nativa.

Você não pode obter isso mais rápido!

Philipp Sander
fonte
parece que você pode, dependendo de algumas coisas, mas obrigado por apontar essa função que eu não conhecia e é realmente mais fácil de ler. :)
Daren
sim! depende de algumas coisas, como disse @Svetoslav Tsolov. eu só queria destacar Arrays.copyOf
Philipp Sander
2
copyOfnem sempre pode substituir arraycopy, mas é apropriado para este caso de uso.
Blaisorblade de
1
NB Se você está olhando para o desempenho, então isso não será tão rápido quanto System.arraycopy (), pois requer alocação de memória. Se isso ocorrer em um loop, as alocações repetidas levarão à coleta de lixo, o que será um grande impacto no desempenho.
Will Calderwood
1
@PhilippSander Apenas para verificar se não estava sendo estúpido, adicionei um código para copiar um array de 1 MB no meu loop de jogo, que quase nunca dispara GC. Com o Array.copyOf () meu DVM estava chamando GC 5 vezes por segundo e o jogo ficou extremamente lento. Acho que é seguro dizer que está ocorrendo alocação de memória.
Will Calderwood
17

Depende da máquina virtual, mas System.arraycopy deve fornecer o mais próximo que você pode obter do desempenho nativo.

Eu trabalhei por 2 anos como um desenvolvedor Java para sistemas embarcados (onde o desempenho é uma grande prioridade) e em todos os lugares que System.arraycopy poderia ser usado, eu o usei / vi principalmente em código existente. É sempre preferível a loops quando o desempenho é um problema. Se o desempenho não for um grande problema, eu iria com o loop. Muito mais fácil de ler.


fonte
Coisas como tamanho e tipo de array (básico vs herdado) parecem afetar o desempenho.
Daren
2
Sim, não é 'desempenho nativo' em si, é por isso que eu disse que uso 'principalmente' onde posso (você notará que geralmente vence a cópia em loop). Acho que o motivo é: quando é uma pequena matriz de um tipo primitivo, o custo de chamada é maior do que o aumento de desempenho. Usar JNI pode degradar o desempenho pelo mesmo motivo - o código nativo em si é rápido, mas chamá-lo de um processo java - nem tanto.
Correção menor, Arrays.copy não é JNI, é intrínseco. Os intrínsecos são muito mais rápidos do que o JNI. Como e quando o compilador JIT o transforma em um intrínseco depende do JVM / compilador usado.
Nitsan Wakart
1
Arrays.copynão existe, Arrays.copyOfé uma função de biblioteca.
Blaisorblade de
12

Em vez de depender de especulações e informações possivelmente desatualizadas, executei alguns benchmarks usando . Na verdade, o Caliper vem com alguns exemplos, incluindo um CopyArrayBenchmarkque mede exatamente essa questão! Tudo que você precisa fazer é correr

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

Meus resultados são baseados no servidor VM de 64 bits Java HotSpot (TM) da Oracle, 1.8.0_31-b13, rodando em um MacBook Pro de meados de 2010 (macOS 10.11.6 com Intel Arrandale i7, 8 GiB de RAM). Não acredito que seja útil postar os dados de tempo brutos. Em vez disso, resumirei as conclusões com as visualizações de apoio.

Em suma:

  • Escrever um forloop manual para copiar cada elemento em um array recém-instanciado nunca é vantajoso, seja para arrays curtos ou longos.
  • Arrays.copyOf(array, array.length)e array.clone()ambos são consistentemente rápidos. Essas duas técnicas são quase idênticas em desempenho; qual você escolher é uma questão de gosto.
  • System.arraycopy(src, 0, dest, 0, src.length)é quase tão rápido quanto e , mas não de forma consistente. (Veja o caso para 50000 s.) Por causa disso, e da verbosidade da chamada, eu recomendaria se você precisar de um controle fino sobre quais elementos são copiados e onde.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Aqui estão os gráficos de tempo:

Tempo para copiar matrizes de comprimento 5 Tempo para copiar matrizes de comprimento 500 Tempos para copiar matrizes de comprimento 50000

200_sucesso
fonte
3
Há algo estranho em copiar int? Parece estranho que o arraycopy seja lento em ints em grandes escalas.
whaleberg de
6

A execução de métodos nativos Arrays.copyOf(T[], int)tem alguma sobrecarga, mas não significa que não seja rápida porque você a executa usando JNI.

A maneira mais fácil é escrever um benchmark e um teste.

Você pode verificar se Arrays.copyOf(T[], int)é mais rápido do que seu forloop normal .

O código de referência daqui : -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()usa JNI (Java Native Interface) para copiar um array (ou partes dele), por isso é extremamente rápido, como você pode confirmar aqui

Rahul Tripathi
fonte
Este código usa int [], você pode tentar com String [] (inicializado com valores diferentes: "1", "2", etc., uma vez que eles são imutáveis
Daren
1
JNI é muito lento . System.arraycopynão usa.
Chai T. Rex
Não, System.arraycopynão usa JNI, que é apenas para chamar bibliotecas de terceiros. Em vez disso, é uma chamada nativa, o que significa que há uma implementação nativa na VM para ela.
spheenik
6

Não é possível que Arrays.copyOfseja mais rápido do que, System.arraycopypois esta é a implementação de copyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
DimaD
fonte
4

System.arraycopy()é uma chamada nativa que copia a operação diretamente na memória. A cópia de memória única seria sempre mais rápida do que o loop for

Nandkumar Tekale
fonte
3
Eu li que para tipos não nativos (qualquer classe criada, como a minha) pode não ser tão eficiente ... e para tamanhos pequenos (meu caso) o manual de loop pode ser melhor ... gostaria de comentar?
Daren
Na verdade, System.arraycopy () tem alguma sobrecarga, então para pequenos arrays (n = ~ 10) um loop é realmente mais rápido
RecursiveExceptionException