Czy .NET to szwajcarski scyzoryk?

0

@Afish

to już lata temu mógł wziąć bibliotekę do optionali i używać ich zamiast zwykłych referencji

aby były prawdziwe optionale, to chcą mieć najpierw dyskryminowane związki ;)
Raczej nie było presji aby wrzucić Optional<T> bo jak sam napisałeś, mogłeś sobie wziąć libkę.

Optionale zmieniają podejście do programowania, a wymuszanie na tak ogromną skalę zmiany podejścia jest raczej bardzo, bardzo słabe.

Nullable ref types są bardziej naturalne dla środowiska - wyobraź sobie jakby Ci teraz cały kod frameworka / corowych bibliotek zmienili na Optional<T> - cały istniejący kod by przestał działać, a podnoszenie wersji wiązałoby się z ogromnymi kosztami dostosowania, pewnie byłaby sytuacja jak z pythonem.

Wystarczyło nie robić nullable references, a kasę w to władowaną przeznaczyć na rozwój GC, żeby wreszcie wyciągnąć porządny interfejs, sensowną implementację i wejść w obszar sparka. Albo w JIT-a, żeby dotnet umiał inline'ować gettery.

skąd pomysł że kasa jest problemem

Spark dla .NET Developerów w przykładach dla .NET Core via Asseco

patrz, w polskim faamgu już to mają.

0
WeiXiao napisał(a):

Nullable ref types są bardziej naturalne dla środowiska - wyobraź sobie jakby Ci teraz cały kod frameworka / corowych bibliotek zmienili na Optional<T> - cały istniejący kod by przestał działać, a podnoszenie wersji wiązałoby się z ogromnymi kosztami dostosowania, pewnie byłaby sytuacja jak z pythonem.

Są, a raczej będą, jak już wszystko będzie ich używało.

WeiXiao napisał(a):

skąd pomysł że kasa jest problemem

Tak słyszałem od ludzi z Microsoftu. Faktem jest, że nad GC siedzą dwie osoby, z czego jedna jest bardzo przeciwna refaktoryzacji kodu i rozbiciu wielkiego pliku na mniejsze. Nie wiem, ile osób pracuje nad JIT-em, ale tam też jest sporo do zrobienia, jeżeli nawet gettery nie są wypłaszczane. Być może to nie kasa, ale powód nie jest istotny, ważny jest fakt, że ta praca bardzo szybko nie posuwa się do przodu.

WeiXiao napisał(a):

patrz, w polskim faamgu już to mają.

Stabilna wyszła dopiero kilka miesięcy temu (chociaż wcześniej był Mobius https://github.com/Microsoft/Mobius albo wersje poglądowe, więc jak ktoś bardzo chciał, to mógł użyć) i teraz już może być trudno urwać kawałek tortu. Jest nadzieja, że to chwyci wraz z nowym Sparkiem, czas pokaże. W każdym razie wydaje się, że to będzie dla nowych graczy, bo jakoś nie widzę zysku z przepisywania Scali/Pythona na C#.

0

@Afish

Tak słyszałem od ludzi z Microsoftu. Faktem jest, że nad GC siedzą dwie osoby, z czego jedna jest bardzo przeciwna refaktoryzacji kodu i rozbiciu wielkiego pliku na mniejsze. Nie wiem, ile osób pracuje nad JIT-em, ale tam też jest sporo do zrobienia, jeżeli nawet gettery nie są wypłaszczane. Być może to nie kasa, ale powód nie jest istotny, ważny jest fakt, że ta praca bardzo szybko nie posuwa się do przodu.

Jak wszystko to, co piszesz w tym wątku nt. GC ma się do tego, co Konrad Kokosa ma na swoim Githubie

Starting from .NET Core 2.0 coupling between Garbage Collector and the Execution Engine itself have been loosened. Prior to this version, the Garbage Collector code was pretty much tangled with the rest of the CoreCLR code. However, Local GC initiative in version 2.1 is already mature enough to start using it. The purpose of the exercise we are going to do is to prepare Zero Garbage Collector that replaces the default one. And then move to implement something more sophisticated.

So this repository currently contains two so-called Zero GCs and one real-world Upsilon GC:

Custom Garbage Collectors for .NET Core - https://github.com/kkokosa/UpsilonGC

0
WeiXiao napisał(a):

Starting from .NET Core 2.0 coupling between Garbage Collector and the Execution Engine itself have been loosened. Prior to this version, the Garbage Collector code was pretty much tangled with the rest of the CoreCLR code. However, Local GC initiative in version 2.1 is already mature enough to start using it. The purpose of the exercise we are going to do is to prepare Zero Garbage Collector that replaces the default one. And then move to implement something more sophisticated.

:D A wiesz, że ten interfejs do GC został wyodrębniony przez jakiegoś praktykanta na szybkości, ma bardzo dużo założeń odnośnie działania GC i zasadniczo nie jest to coś, z czym wiąże się jakieś sensowne plany?

6

Widzę, że moje grymaszenie stało się już znane na forum, ludzie nawet śmieszkują o HKT :) Mówiłem w tym temacie, że dotnet kiepsko radzi sobie z optymalizacjami, ale Bartosz Adamczewski sprawia, że aż mi łapki opadają. Dotnet nie potrafi porządnie inline'ować getterów, nie radzi sobie ze strukturami, ale to jest naprawdę mocne: https://www.facebook.com/groups/net.developers.poland/permalink/1763943587120322/

Jeżeli ktoś nie ma dostępu do grupy, to Bartek pokazuje, że kod a[i] += i + x jest o ponad 30% wolniejszy od kodu a[i] = a[i] + i + x.

Jest 2021 rok, a w dotnecie ciągle trzeba przejmować się takimi bzdurami.

1

.NET Core jest szybki a C# całkiem fajny. Jak ktoś wie jak to wszystko działa od podszewki oraz jest dobrym algorytmikiem to nie ma powodu przejmować się szybkością platformy .NET.

0
Afish napisał(a):

Jeżeli ktoś nie ma dostępu do grupy, to Bartek pokazuje, że kod a[i] += i + x jest o ponad 30% wolniejszy od kodu a[i] = a[i] + i + x.

Jest 2021 rok, a w dotnecie ciągle trzeba przejmować się takimi bzdurami.

Tu poprę @some_ONE. Owszem, to jest żałosne, że to jest tak skopane, ale ma niewielki wpływ na codzienne życie typowego programisty.
A z drugiej strony, może oni to specjalnie robią, żeby ludzie więcej Azura palili? :P

0

No jak niewielki? Przecież to jest normalny powszechny kod.

1

Nie mówię, że jest nienormalny, ale żeby to miało rzeczywisty wpływ na całościową wydajność, to musi być jakiś mocno bazujący na liczeniu soft.

1
private int B;
private List<int> Data;

[GlobalSetup]
public void Setup()
{
	var rnd = new Random();
	B = rnd.Next(42);
	Data = Enumerable.Range(0, 10_000_000).Select(_ => rnd.Next(100)).ToList();
}

[Benchmark]
public void Test1()
{
	var arr = Data.ToArray();

	for (int i = 0; i < arr.Length; i++)
	{
		arr[i] += i + B;
	}
}

[Benchmark]
public void Test2()
{
	var arr = Data.ToArray();

	for (int i = 0; i < arr.Length; i++)
	{
		arr[i] = arr[i] + i + B;
	}
}
.NET Core SDK=5.0.102
[Host]     : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
DefaultJob : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT


| Method |     Mean |    Error |   StdDev |
|------- |---------:|---------:|---------:|
|  Test1 | 47.64 ms | 0.682 ms | 0.604 ms |
|  Test2 | 46.55 ms | 0.772 ms | 0.858 ms |

?

0

@WeiXiao:
Nie porównuj operacji na liście do operacji na tablicach.

    [MemoryDiagnoser]
    [Orderer(SummaryOrderPolicy.FastestToSlowest)]
    [RankColumn]
    public class LoopBenchmarks
    {
        private int[] _array;
        private const int ArraySize = 1_000_000;
        private List<int> _list;
        private static int x = 2;
        
        [GlobalSetup]
        public void Setup()
        {
            _array = new int[ArraySize];
            _list = new List<int>(ArraySize);
            
            for (int i = 0; i < ArraySize; i++)
            {
                _array[i] = i;
                _list.Add(i);
            }
        }

        [Benchmark]
        public void SlowArrayLoop()
        {
            var a = _array;

            for (int i = 0; i < ArraySize; i++)
            {
                a[i] += i + x;
            }
        }
         
        [Benchmark]
        public void FastArrayLoop()
        {
            var a = _array;

            for (int i = 0; i < ArraySize; i++)
            {
                a[i] = a[i] + i + x;
            }
        }
        
        [Benchmark]
        public void SlowListLoop()
        {
            var l = _list;

            for (int i = 0; i < ArraySize; i++)
            {
                l[i] += i + x;
            }
        }
        
        [Benchmark]
        public void FastListLoop()
        {
            var l = _list;

            for (int i = 0; i < ArraySize; i++)
            {
                l[i] = l[i] + i + x;
            }
        }
    }

screenshot-20210206221200.png

screenshot-20210206221233.png

screenshot-20210206221248.png

1

@urke:

no ale przecież w twoim benchu dane nie są niezależne pomiędzy testami, bo masz arr przez refa.

var bencher = new LoopBenchmarks();
bencher.Setup();
bencher.FastArrayLoop();
bencher.SlowArrayLoop();

Wywaliłem tego const inta i zamieniłem go na losowanego, a w dodatku generuje też wartości do tablicy losowo aby może branch predictor nie mieszał

private int[] _array1;
private int[] _array2;
private const int ArraySize = 1_000_000;
private int x;

[GlobalSetup]
public void Setup()
{
	var rnd = new Random();
	x = rnd.Next(56);
	_array1 = new int[ArraySize];
	_array2 = new int[ArraySize];

	for (int i = 0; i < ArraySize; i++)
	{
		var val = rnd.Next(100);
		_array1[i] = val;
		_array2[i] = val;
	}
}

[Benchmark]
public void SlowArrayLoop()
{
	var a = _array1;

	for (int i = 0; i < ArraySize; i++)
	{
		a[i] += i + x;
	}
}

[Benchmark]
public void FastArrayLoop()
{
	var a = _array2;

	for (int i = 0; i < ArraySize; i++)
	{
		a[i] = a[i] + i + x;
	}
}
CPU, 16 logical and 8 physical cores
.NET Core SDK=5.0.102
[Host]     : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
DefaultJob : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT


|        Method |     Mean |   Error |  StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------- |---------:|--------:|--------:|-----:|------:|------:|------:|----------:|
| FastArrayLoop | 778.9 us | 3.54 us | 2.96 us |    1 |     - |     - |     - |       1 B |
| SlowArrayLoop | 972.8 us | 4.56 us | 4.04 us |    2 |     - |     - |     - |         - |

20% diff, nie wiem czemu wcześniej nic nie było różnicy.

przy 10 000 000

|        Method |     Mean |     Error |    StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------- |---------:|----------:|----------:|-----:|------:|------:|------:|----------:|
| FastArrayLoop | 8.146 ms | 0.0337 ms | 0.0299 ms |    1 |     - |     - |     - |         - |
| SlowArrayLoop | 9.510 ms | 0.0242 ms | 0.0214 ms |    2 |     - |     - |     - |         - |

no ale nadal, przed chwilą było ponad 30%, a nagle mamy 20%?

wypadałoby aby więcej kodu było wrzucane w tych przykładach.

2

dobra, widzę co poczarowane tam było

wystarczy zmienić for (int i = 0; i < ArraySize; i++) na coś bardziej przewidywalnego przy iterowaniu po tablicy pokroju
for (int i = 0; i < a.Length; i++)

i nagle mamy ogromny performance gain

Method Mean Error StdDev
SlowArrayLoop 9.111 ms 0.0546 ms 0.0484 ms
FastArrayLoop 8.325 ms 0.1555 ms 0.1454 ms

9% diff

public class LoopBenchmarks
{
    private int[] _array1;
    private int[] _array2;
    private const int ArraySize = 10_000_000;
    private int x;

    [GlobalSetup]
    public void Setup()
    {
        var rnd = new Random();
        x = rnd.Next(56);
        _array1 = new int[ArraySize];
        _array2 = new int[ArraySize];

        for (int i = 0; i < ArraySize; i++)
        {
            var val = rnd.Next(100);
            _array1[i] = val;
            _array2[i] = val;
        }
    }

    [Benchmark]
    public void SlowArrayLoop()
    {
        var a = _array1;

        for (int i = 0; i < a.Length; i++)
        {
            a[i] += i + x;
        }
    }

    [Benchmark]
    public void FastArrayLoop()
    {
        var a = _array2;

        for (int i = 0; i < a.Length; i++)
        {
            a[i] = a[i] + i + x;
        }
    }
}

nie wiem co o tym sądzić

0

U mnie nie ma żadnej różnicy względem tego, czy operuje na tej samej tablicy, czy na 2 różnych - zawsze jest wolniej o jakąś 1/3.

0

@urke

co to znaczy niejako? ile mniej więcej wychodzi na randomowych danych?

1

U mnie czasy takie same, zgodnie z tym, co @WeiXiao napisał.

Wydaje się zatem, że rewelacje Bartosza nie są zbyt rzetelne, więc nie ma się też co nimi ekscytować.

0

Najwyraźniej JIT w dotnecie jest tak nieprzewidywalny, że nawet nie da się porządnie zmierzyć, czy kod jest w końcu wolniejszy tylko o 10%, czy może o 30%.

2

@Afish

a może wypadałoby po prostu robić benche na różnym sprzęcie(chociaż nie wiem czy Bartosz tak nie zrobił, ale nie wygląda na to), tak samo jak przy benchmarkowaniu zestawów komputerowych - ludzie sprawdzają je na wielu różnych softach realizujących różne typy zadań (video, audio, compression, etc) czy na wielu różnych grach.

nawet na linuxie (server, nie pc) przetestowałem ten mój ostatni kodzik i wychodzi mi 12.5% diff, czyli podobnie jak 9% wcześniej

BenchmarkDotNet=v0.12.1, OS=debian 10
Intel Core Processor (Haswell, no TSX), 1 CPU, 1 logical core and 1 physical core
.NET Core SDK=5.0.102
[Host]     : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
DefaultJob : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT


|        Method |     Mean |    Error |   StdDev |
|-------------- |---------:|---------:|---------:|
| SlowArrayLoop | 24.44 ms | 0.483 ms | 1.099 ms |
| FastArrayLoop | 21.22 ms | 0.418 ms | 0.786 ms |


wx@xdddddd ~/bench# cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 60
model name      : Intel Core Processor (Haswell, no TSX)
stepping        : 1
microcode       : 0x1
cpu MHz         : 2095.076
cache size      : 16384 KB
physical id     : 0
siblings        : 1
core id         : 0
cpu cores       : 1
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm cpuid_fault invpcid_single pti tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear
\/ lol
bugs            : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
0

.NET C# działa bardzo szybko. Jak coś działa wolno to profiler pokaże fragmenty kodu które mogą spowalniać program. Zawsze można użyć kodu niezarzadzanego.

0
WeiXiao napisał(a):

a może wypadałoby po prostu robić benche na różnym sprzęcie(chociaż nie wiem czy Bartosz tak nie zrobił, ale nie wygląda na to), tak samo jak przy benchmarkowaniu zestawów komputerowych - ludzie sprawdzają je na wielu różnych softach realizujących różne typy zadań (video, audio, compression, etc) czy na wielu różnych grach.

Ale to nie ma znaczenia, tu chodzi o fakt, że te dwa kody dają różne wyniki.

0

Tak popatrzyłem, co piszecie i wpadła mi do głowy możliwa przyczyna obserwowanych przez Was różnic. Co może wpłynąć na obie pętle. Otóż można dla nich uzyskać różne wyniki:
screenshot-20210207152808.png
Jeszcze trochę podumałem i dostałem:
screenshot-20210207152901.png
Kody obydwu przykładów:
FastSlowLoop.7z

0

@Mikan: ale nadal te czasy, skąd u niego takie różnice

0

@WieXiao: Ciekawe, czyżby u Ciebie były inne? Mam Windows 10, VS 2019 Community, Intel Core i5-4430 3.00 GHz 3.00 GHz, 16,0 GB RAM, system 64bit.

0

@Sadam2: cóż to za pytanie "czyżby U ciebie były inne?" a o czym były dwie ostatnie strony w tym wątku :P

0

@WeiXiao: Może Cię nie zrozumiałem. Inne niż u mnie.Nie odnoszę się do "dwóch stron". Tylko do tej.

0

w końcu, już nie mogłem się doczekać crossplatformowego desktopa

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