C# vs Java - przetwarzenie dużych ilości danych.

0

Witam.

Moim zamiarem jest wykonanie webservice, który będzie służył do przetwarzania dużych ilości danych. Z tego względu stoję przed wyborem technologii, w której to pisać. Przeprowadziłem mały test na dynamicznej strukturze danych i wygląda na to że Java zupełnie sobie z tym nie radzi.
Jeśli mam gdzieś błąd to proszę o poprawkę.

Kod Java:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
public class Test {
  
    public static void main(String[] args){
        
        int count = 1000000;
        List<Integer> list1 = new ArrayList<Integer>();
        Random generator = new Random();

        for(int i = 0; i < count; i++) {
            list1.add(generator.nextInt());
        }

        List<Integer> list2 = new ArrayList<Integer>();

        for(int i = 0; i < count; i++) {
            list2.add(generator.nextInt());
        }

        long startTime = System.nanoTime();
        List<Integer> list3 = new ArrayList<Integer>();
        
        for(int i = 0; i < count; i++) {
            list3.add(list2.get(i) + list1.get(i));
        }
        long endTime = System.nanoTime();

        long duration = (endTime - startTime);
        double seconds = (double) duration / 1000000000.0;
        System.out.println("That took " + seconds + " seconds");
    }
}

Wynik:
That took 0.87815511 seconds


Kod C#:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            int count = 1000000;
            List<int> list1 = new List<int>();
            Random rand = new Random(count);
            for (int i = 0; i < count; i++)
            {
                list1.Add(rand.Next(count));
            }
            
            List<int> list2 = new List<int>();
            Random rand2 = new Random(count);
            for (int i = 0; i < count; i++)
            {
                list2.Add(rand2.Next(count));
            }

            Stopwatch watch = Stopwatch.StartNew();
            List<int> list3 = new List<int>();
            for (int i = 0; i < count; i++)
            {
                list3.Add(list1[i] + list2[i]);
            }
   
            watch.Stop();
            Console.WriteLine("That took {0}ms", watch.ElapsedMilliseconds);

        }
    }
}

Wynik:
That took 18ms

1

ile razy to uruchamiales? ten wynik to srednia czy wynik jednego wykonania? Jezeli jednego to uruchom ten program tak z 1000 razy to wtedy bedziesz miec jakies menchmark (chociaz tutaj to nie tak za bardzo jest co do benchmarkowania)

0

Ja bym jeszcze zrobił test w Scala i Ceylon i zainteresował się tymi językami.

0

Uruchomiłem to parędziesiąt razy i wynik zawsze był taki sam, czyli Java ~1s, C# ~0.02s.

Wynik w C# jest zadowalający, Jednak ludzie piszą ogromne aplikacje w Java, które pewnie przetwarzają duże ilości danych i dlatego może coś w tym "mini programiku" jest źle napisane. Oczywiście rozwiązaniem problemu byłoby List<Integer> list3 = new ArrayList<Integer>(1000000); ale ja nie wiem ile tych danych będzie docelowo ;-)

5

Różnica wynika z tego, że w Javie używasz opakowanych typów (referencyjnych), a w C# bezpośrednio prymitywów w liście.

Najlepiej po prostu zamiast intów użyj jakichś klas, bo zwykle i tak się używa klas w kodzie biznesowym.

4

Java niestety w tym przykładzie daje duży narzut na autoboxing prymitywów. Zauważ ze to jest List<Integer> a nie List<int>, więc sporo czasu zlatuje na alokacje na stercie. Kod porównywalny do tego z C# to byłoby raczej:

        int count = 1000000;
        Random generator = new Random();
        int[] list1 = new int[count];
        for (int i = 0; i < count; i++) {
            list1[i] = generator.nextInt();
        }
        int[] list2 = new int[count];
        for (int i = 0; i < count; i++) {
            list2[i] = generator.nextInt();
        }
        long startTime = System.nanoTime();
        int[] list3 = new int[count];
        for (int i = 0; i < count; i++) {
            list3[i] = list2[i] + list1[i];
        }
        long endTime = System.nanoTime();
        long duration = (endTime - startTime);
        double ms = (double) duration / 1000000.0;
        System.out.println("That took " + ms + " ms");

co u mnie daje czas wykonania ~4ms

Ale w praktyce tej różnicy nie będzie jeśli i tu i tu użyjesz jakichś obiektów.

0

W Javie możesz użyć kontenerów prymitywnych danych zamiast tablic - będzie wygodniejsze, ale pewnie trochę wolniejsze:

https://commons.apache.org/dormant/commons-primitives/
https://www.eclipse.org/collections/
http://trove.starlight-systems.com/
http://labs.carrotsearch.com/hppc.html

W C++ taki test pewnie będzie szybszy - do czasu aż będziesz próbował to zrobić wielowątkowo.

4

juz pomijajac ze wynik twojego testu jest bezuzyteczny z co najmniej 10 powodow ;) to ciezko mi sobie wyobrazic scenariusz w ktorym wybor miedzy java a c# mial byc motywowany wydajnoscia.

1

@MOD-y - a w ogóle, to sprawdzałeś chociaż wersję release?

0

Poza tym jak przetwarzasz dużą ilość danych to zazwyczaj robisz to strumieniowo, a nie zachłannie tak jak w ww. programach. Więc taki mocno bez sensu ten benchmark.

0

Bardzo duże ilości danych przetwarza się partiami w środowisku rozproszonym i służą do tego narzędzia typu: http://spark.apache.org/

1

Jak już robisz benchmarki startTime - endTime to chociaż mierz zadania tak z 5-10 sekund (czyli rób na większych liczbach).
Czasy poniżej to zwykłe bzdury. (za duży wpływ losowych czynników, JIT kompilacji itp.)

Wpływ JIT zobaczysz jak np. powtórzysz w pętli zadanie wiele razy:

public class Test {
    public static void main( final String ... args ) {
        for (int i=0; i< 500 ; i++) {
            final double ms = testIt();
            if ( i %100 == 0) { //co setny wynik wrzucamy na konsole
                System.out.println("That took " + ms + " ms");
            }

        }
    }

    private static double testIt() {
        int count = 1000000;
        Random generator = new Random();
        int[] list1 = new int[count];
        for (int i = 0; i < count; i++) {
            list1[i] = generator.nextInt();
        }
        int[] list2 = new int[count];
        for (int i = 0; i < count; i++) {
            list2[i] = generator.nextInt();
        }
        long startTime = System.nanoTime();
        int[] list3 = new int[count];
        for (int i = 0; i < count; i++) {
            list3[i] = list2[i] + list1[i];
        }
        long endTime = System.nanoTime();
        long duration = (endTime - startTime);
        double ms = (double) duration / 1000000.0;
        return ms;

    }
}

That took 4.354265 ms ( to co zmierzyłeś)
That took 0.784991 ms
That took 0.72118 ms
That took 0.738926 ms
That took 0.738171 ms (to co się dzieje po rozgrzaniu JVM)

Poza tym prawdopodobnie to g... jest to warte - list3 jest nigdzie nie przekazane.

Chcesz robic benchmarki w Javie to poczytaj/ pooglądaj o JMH

W video jest pokazane kilka pułapek - dlaczego benchmark Cie kłamie i co zrobić żeby nie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1