Laden...
Avatar #avatar-2894.jpg
gfoidl myCSharp.de - Team
Entwickler in der Gasmotorenbranche (nicht nur Software) Waidring Dabei seit 07.06.2009 6.911 Beiträge
Benutzerbeschreibung
FIS-Speedski (S-DH) world champion 2015 3x FIS-Speedski (S-DH) overall worldcup winner, 2008/09, 2009/10, 2012/13 5x Subaru Velocity Challenge winner, 2009, 2010, 2013, 2014, 2015 7x PopKL Verbier winner, 2009 2x, 2010 2x, 2011 2x, 2013 1x 27 victories in 69 international Speedski-races v_max = 208,333 km/h http://de.wikipedia.org/wiki/Günther_Foidl

Forenbeiträge von gfoidl Ingesamt 6.911 Beiträge

06.01.2020 - 18:04 Uhr

Hallo MrSparkle,

"Circular References" (Kreisreferenzen?, wie heißt das auf Deutsch?)

zirkuläre Abhängigkeit

mfG Gü

02.01.2020 - 10:05 Uhr

Hallo Abt,

wird es aber nicht empfohlen Kestrel direkt gegen das Internet zu exposen

Gilt diese Empfehlung auch noch für .NET Core 3.x?
In Kestrel wurden etliche Server-Features nachgeürstet, so dass er zu einem richtigen Frontend-Server wurde, daher gilt diese Empfehlung imho nicht mehr so streng wie zu Zeiten von .NET Core 2.1 (und früher). Ein paar Einschränkungen gibt es allerdings, siehe Doku, diese sind aber eher Schönheitsfehler.

Ich weiß jedoch nicht ob Kestrel real und praktisch betrachtet direkt dem Internet ausgesetzt werden soll. Vllt. hast du hier bessere Infos als ich.

mfG Gü

23.12.2019 - 16:34 Uhr

Hallo Darkblue94,

Array.Sort ist eine void-Methode, daher lässt sich das nicht kompilieren.
Siehe aber oben und lies dir den verlinkten Thread durch.

mfG Gü

23.12.2019 - 16:28 Uhr

Hallo Darkblue94,


textBox4.Text = $"{zahlen[0]} {zahlen[1]} {zahlen[2]}";

Ist naiv umgesetzt, auch dein Code schon. Denn bedenke dass es einen Laufzeitfehler gibt, falls in textBox1.Text keine Zahl, sondern sonst ein Text steht. Besser ist die Verwendung von int.TryParse (cf. How to: Convert a String to a Number).

Wenn dann allgemein aus einem Array eine Zeichenfolge erstellt werden soll, kann bei wenigen fixen Array-Elemente wie oben gezeigt vorgegangen werden. Bei wenigen (~ weniger als 10) und nicht fixen Array-Elemente z.B. so


string result = "";

foreach (int i in array)
    result += i.ToString() + " ";

Wegen [FAQ] Besonderheiten der String-Klasse (immutabler Referenztyp mit Wertsemantik) sollte bei meheren Array-Elemente z.B. der StringBuilder verwendet werden.


var stringBuilder = new StringBuilder();

foreach (int i in array)
    sb.Append(i).Append(" ");

string result = stringBuilder.ToString();

mfG Gü

22.12.2019 - 15:19 Uhr

Hallo T-Virus,

dazu eine kleine Ergänzung...

die Vektoriersierung in Core Span.IndexOf (die Erweiterungsmethode, eigentlich MemoryMarshal.IndexOf<T>(ROS, ROS)) schaut ob T ein byte ist wenn ja nimmt es die optimierte Version von SpanHelper.IndexOf(byte...) um für das erste Element einen Treffer zu erzielen. Ist dieser vorhanden, so wird der Rest mit dem Suchmusterrest (das je erste Element wurde ja schon gematcht) per SpanHelper.SequenceEqual verglichen.

SequenceEqual ist wiederum der generische "Einstiegspunkt", der prüft ob T ein byte ist und falls ja den spezialisierten Code nimmt. Falls nicht muss der Vergleich generisch durchgefürht werden, d.h. einfach per Schleife (ist auch abgewickelt / "unrolled") drüberriterieren und per EqualityComparer<T>.Default auf Gleichheit prüfen. Das ist aufwändig.

Wird hingegen der byte-spezialisierte Pfad genommen, so können mehre Optimieren durchgeführt werden:* vektorisierter Vergleich -- für SSE sind dazu mind. 16 bytes nötig, für AVX 32, für AVX-512 64, bei ARM analog

  • statt byte für byte, können z.B. je 8 bytes per long verglichen werden, dann je 4 bytes per int, je 2 bytes per short -- das ist auch Endianess-sicher, da es nur um gleich od. ungleich geht

Anmerkung:
Das gilt nicht nur für byte, sondern allgemein für alle T deren Größe (sizeof) 1 ist. Somit auch für sbyte.
Für sizeof(T) == 2 (char, short, ushort) wird eine ähnliche Spezialisierung vorgenommen.

In der Aufgabe hier hat das Suchmuster die Länge 15, ist also für den vektorisierten SequenceEqual-Pfad zu kurz, dafür können jedoch die im 2. Punkt genannten Tricks angewandt werden. D.h. idealisiert und vereinfacht (dem Amdahlsches Gesetz nicht ganz genüge getan) sollte die Laufzeit dadurch 4...8x verkürzt werden.

den Optimierungen in .NET Core

Hier v.a. das besser interne Handling von Spans, welche der JIT direkt unterstützt und mehr od. weniger nur Prüfungen auf Einhaltung der Grenzen (bound checks) und Pointer-Bewegungen sind. Ziemlich ähnlich wie es bei sz-Arrays der Fall ist (nur dass eben keine Sub-Arrays, Array-Kopien, etc. nötig sind).
Ob diese direkte Unterstützung zu .NET 4.8 zurückportiert wurde weiß ich gerade nicht.

Dann gab es eine Menge Optimierungen in der Thread-Infrastruktur auf welchen die parallen Schleifen beruhen.

mfG Gü

21.12.2019 - 23:15 Uhr

Hallo 4dk2,

Es gibt sicher noch bessere, und ich bin mir sicher UNSAFE{} wird am schnellsten sein.

Pauschal bin ich mir da wiederum nicht so sicher. Allgemein:* Egal ob unsafe od. Unsafe, die Runtime kann keine Sicherheiten in Bezug auf Indexgrenzen, Typsicherheit, etc. mehr gewährleisten und das kann potentielle Bugs mit sich bringen. Will man diese Sicherheit dennoch, so sind indizierte Zugriffe explizit zu validieren, etc. und schon ist der vermeintliche Vorteil von unsicherem Code wieder weg.

  • Es gibt ein paar "Tricks" mit denen sicherer Code genauso effizient wie unsicherer Code ist, da der JIT gleichen bzw. gleichwertigen Maschinencode erzeugt. Da denke ich v.a. an das Vermeiden von Bound-Checks bei indizierten Zugriffen.
  • ...

da gäbe es noch viel zu schreiben, aber das geht an diesem Thema vorbei.


Statt #if isCORE (und dem #define) kannst du die vordefinierten Präprozessor Symbole NETCOREAPP (mit od. ohne Version) auch verwenden. Siehe dazu Target frameworks in SDK-style projects / How to specify target frameworks.

Vorab: wie Abt erwähnt hat, hab ich keine Fußnote für den schnellsten Code 😉
V.a. dann nicht wenn ich das eher schnell runtertippe, als wirklich als Aufgabe optimierten Code zu liefern.

Aber als ich über den von dir geposteten Code geflogen bin, konnte ich nicht glauben dass dieser so wesentlich schneller ist, da* in IndexOfSequence jedesmal ein Array alloziiert um dann dorthin zu kopieren -- das Array hat immer die gleich Größe, daher könnte einmal alloziiert und dann wiederverwendet werden od. um die Allokation überhaupt zu sparen stackalloc (vorzugsweise zusammen mit Span<byte> verwendet werden damit die Bound-Checks erhalten bleiben)

  • bei .NET Full in gleicher Methode Linq für SequenceEqual verwendet wird und das kann nicht schneller sein als eine vektorisierte Variante wie sie per Span gegeben ist. Auch wenn Linq prüft ob es eine IList<T> ist und dann per for-Schleife iteriert. Es wird dann jedesmal ein Vergleich mit EqualityComparer<byte>.Default durchgeführt. Dieser wird in neueren Versionen des JITs (.NET Core 2.1 denke ich und dann ab .NET 4.8 da diese Optimierungen zurückportiert wurden) devirtualisiert, da byte ein Werttyp ist. Nichtsdestotrotz kann das nicht schneller als vektorisierter Code sein, da es Element für Element (wenn auch indiziert) passiert.

  • für jeden Aufruf von IndexOfSequence eine List<int> alloziiert wird -- gut bei meiner Variante wird jedesmal der vom Compiler generierte Iterator alloziiert, aber das ist nur ein Objekt, während es bei der Liste zwei sind, eins für die Liste selbst und eins für das Array, welches die Liste intern verwendet

  • i = Array.IndexOf(buffer, pattern[0], i + 1); "rückt" nur um i weiter wenn es ein Match gab, anstatt i + searchPattern.Length, erinnert ein wenig an Schlemiel dem Maler (;-))

  • Array.IndexOf sucht vom startIndex bis zum Ende des Arrays. Das ist bei sequentiellem Code kein Problem, aber bei der parallelen Version, da die obere Index-Schranke range.Item2 nicht berücksichtigt wird, d.h. es wird potentiell in einer anderen Range weitergesucht obwohl das nicht nötig wäre. (Es wird dann geprüft ob der gefunden Index innerhalb der gültigen Range ist, daher ist das Ergebnis korrekt.)

  • ebenso wird taskCurrent alloziiert, kann vermieden werden und stattdessen direkt mit source gearbeitet werden

Kurz also jede Menge vermeidbarer Allokationen und sequentieller Code statt vektorisiertem.

Grunsätzlich sollte .NET Core "schneller" sein als .NET Full, da dort die Entwicklung vorangetrieben wird. Bei einem Vergleich sollte jedoch auch Tiered-Compilation berücksichtigt werden, die in .NET Core standardmäßig aktiviert ist. D.h. dort wird während einer "Startphase" der IL-Code nur minimal optimiert, so dass eben der Programmstart rasch fortschreiten kann. Erst wenn eine Methode mehrmals (aktuell 30x) ausgeführt wurde und auch erst nach Ende der Startphase (~150ms nachdem die letzte Methode geJITet wurde) wird diese erneut geJITet, diesmal aber mit maximaler Optimierung (im Sinne des dem JIT möglichen).
Wurde also der Vergleich rein mit meinem simplen Ansatz aus dem Projekt oben durchgeführt, so hakt es schon destwegen.

Soweit die Theorie, die ich (natürlich) validiert habe.

Dass nicht alle Tests vom oben angehängten Projekt mit deiner Lösung passieren lasse ich hier außer Acht, sollte aber wenn schon verglichen wird auch berücksichtigt werden. Daher hab ich für die Vergleiche unten die nötigen null-Checks, etc. eingebaut, so dass die Tests passieren und es vergleichbarer ist.

Dein Code sortiert die Liste, das hab ich bei den Vergleichen entfernt, damit es vergleichbarer ist und da es lt. Aufgabenstellung nicht gefordert ist.
Das Sortieren wäre für alle Varianten ohnehin gleicher Aufwand.

  
if (totalLength < ThreshouldForParallel)  
{  
    //nicht per tasks...  
    indices = new List<int>(source.IndexOfSequence(searchPattern));  
    return indices.Count > 0;  
}  
else  
{  
    // ...  
}  
  

Wenn IndexOfSequence als Ergebnis eine List<int> liefert, warum weist du dann das Ergebnis nicht direkt indices zu?
Da hast du wohl die Codes falsch zusammenkopiert 😉

Genauso ist "die Else" hier nicht nötig, da beim return die Methode ohnehin verlassen wird.

Wie vorhin erwähnt ist einfaches Messen nicht so einfach bzw. ist die Gefahr groß, dass die Ergebnisse nicht sinnvoll verwertbar sind. Daher ist z.B. BenchmarkDotNet (BDN) vorzuziehen, da mit diesem Werkzeug etliche Fallstricke berücksichtigt werden (ist aber dennoch kein Allheilmittel, denn die Ergebnisse müssen auch korrekt gelesen werden (können)).

Sequentieller Code-Pfad

Zuerst eine Betrachtung für rein sequentiellen Code, also ohne Parallel.ForEach.


|        Method |       Runtime |     Mean |   Error |  StdDev | Ratio |       Gen 0 | Gen 1 | Gen 2 |   Allocated |
|-------------- |-------------- |---------:|--------:|--------:|------:|------------:|------:|------:|------------:|
|  AllIndicesOf |      .NET 4.8 | 170.0 ms | 1.88 ms | 1.76 ms |  0.26 |           - |     - |     - |      2731 B |
| AllIndicesOf2 |      .NET 4.8 | 654.1 ms | 4.25 ms | 3.97 ms |  1.00 | 116000.0000 |     - |     - | 366754144 B |
| AllIndicesOf4 |      .NET 4.8 | 167.8 ms | 0.95 ms | 0.89 ms |  0.26 |           - |     - |     - |      2731 B |
|               |               |          |         |         |       |             |       |       |             |
|  AllIndicesOf | .NET Core 3.0 | 160.1 ms | 0.99 ms | 0.93 ms |  0.73 |           - |     - |     - |       645 B |
| AllIndicesOf2 | .NET Core 3.0 | 218.8 ms | 1.75 ms | 1.64 ms |  1.00 |  44666.6667 |     - |     - | 140643485 B |
| AllIndicesOf4 | .NET Core 3.0 | 159.2 ms | 1.16 ms | 0.97 ms |  0.73 |           - |     - |     - |       244 B |


AllIndicesOf ist dabei der Code den ich oben gepostet haben.
AllIndicesOf2 ist der von dir gepostete Code (bei dir hieß es AllIndicesOfFaster)
AllIndicesOf4 ist eine andere Variante die ich gerade erstellt habe und die ich momentan als ganz brauchbar empfinde (ohne auf "unsafe" zurückzugreifen).

AllIndicesOf3 gibt es auch noch und entspricht AllIndicesOf nur dass der Iterator selbst als ref struct ausgeführt wurde um die Allokation vom Iterator-Objekt und den Interface-Dispatch bei MoveNext (von der foreach-Schleife) zu vermeiden. Die Allokation wurde zwar geringer, aber der Laufzeit-Gewinn ist im Bereich des Messfehlers, da der "heiße Teil" das IndexOf ist, daher nicht in den Ergebnissen angeführt.

In den Ergebnissen ist zum Einen zu sehen dass .NET Full (hier 4.8 statt 4.7.2. (das hab ich nicht installiert)) wie erwartet nicht schneller als die .NET Core Version ist. Sogar ziemlich gegenteilig (v.a. wegen Span-basierten / vektorisiertem IndexOf und SequenceEqual).

Weiters ist zu sehen, dass AllIndicesOf2 jede Menge Allokationen hat und aufgrund der suboptimalen Implementierung doch recht klar langsamer ist.
Die Eingangs erwähnte Theorie ist zumindest beim sequentiellen Pfad bestätigt.

Paralleler Code-Pfad


|        Method |       Runtime |      Mean |     Error |    StdDev |    Median | Ratio | RatioSD |     Gen 0 | Gen 1 | Gen 2 |   Allocated |
|-------------- |-------------- |----------:|----------:|----------:|----------:|------:|--------:|----------:|------:|------:|------------:|
|  AllIndicesOf |      .NET 4.8 | 42.610 ms | 0.5172 ms | 0.4838 ms | 42.533 ms |  1.33 |    0.08 |         - |     - |     - |       14 KB |
| AllIndicesOf2 |      .NET 4.8 | 29.801 ms | 0.6081 ms | 1.7449 ms | 29.199 ms |  1.00 |    0.00 | 4843.7500 |     - |     - | 14929.02 KB |
| AllIndicesOf4 |      .NET 4.8 | 42.765 ms | 0.5323 ms | 0.4979 ms | 42.821 ms |  1.33 |    0.08 |         - |     - |     - |        8 KB |
|               |               |           |           |           |           |       |         |           |       |       |             |
|  AllIndicesOf | .NET Core 3.0 | 42.147 ms | 0.8226 ms | 0.8802 ms | 42.177 ms |  4.23 |    0.16 |         - |     - |     - |    11.79 KB |
| AllIndicesOf2 | .NET Core 3.0 |  9.916 ms | 0.1976 ms | 0.2958 ms |  9.785 ms |  1.00 |    0.00 | 1859.3750 |     - |     - |  5725.27 KB |
| AllIndicesOf4 | .NET Core 3.0 | 42.019 ms | 0.7827 ms | 0.8038 ms | 41.805 ms |  4.22 |    0.16 |         - |     - |     - |     6.22 KB |

Ui, da schaut es sehr unerwartet aus...*
Dass es auf einmal gravierend langsamer ist entspricht überhaupt nicht dem was ich erwartet habe und entbehrt(e) sich jeglicher Logik.
Nach eingehender Untersuchung (PerfView, VTune) konnte ich einen "Bug" in .NET Core als Übeltäter ausfindig machen, der im Span-basierten Code zum Tragen kommt, während diesem Umstand mit der Array-basierten Methode aus dem Weg gegangen wurde.
Bug ist in "" da der Code ja fehlerfrei funktioniert, aber dennoch ein Bug ist, da die Laufzeit-Ansprüche verfehlt wurden.
Das Schöne an .NET Core ist dass es Open Source ist, somit kann auch gleich die Lösung für den Bug vorgeschlagen werden.

Einen Bug im Framework zu finden ist nicht so einfach, nicht da wenige vorhanden sind, sondern weil -- zumindest ich -- zuerst den Fehler überall anders suche.
Z.B. sind bei den Vergleichen zwischen den beiden Varianten bestimmte verwendete (Framework-) Methoden auszuschließen, da die Implementierung gleich ist.
Span<T>.IndexOf(ROS, ROS) führt zuerst ein IndexOf(ROS, ROS[0]) (also nach dem ersten Element des Suchmusters) durch und bei einem Treffer wird mit dem restlichen Suchmuster verglichen (ohne ROS[0], das wurde ja schon gefunden).
AllIndicesOf2 macht das gleichwertig, nur mit Array<T>.IndexOf(T[], T, int, int) und das wiederum -- zumindest in .NET Core -- delegiert zur Span-Version. AllIndicesOf2 vergleicht mit dem gesamten Suchmuster, obwohl nur searchPattern[1..] nötig wäre.
Summa summarum ist dieser Teil gleichwertig. Der JIT sollten das bischen Overhead vom weiterdelegieren wegoptimieren können und ein paar CPU-Zyklen mehr od. weniger sind im Bereich der Messgenauigkeit.
Dass nun dieser Bug genau bei IndexOf vorhanden ist, daran dachte ich vorerst nicht.

Wird obiger Benchmark (ohne den Fix) mit einer source ausgeführt, die lauter 0 enthält, außer dort wo das Suchmuster hinkopiert wurde, so ist AllIndicesOf2 wesentlich langsamer.


|        Method |      Mean |     Error |    StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------- |----------:|----------:|----------:|------:|------:|------:|------:|----------:|
| AllIndicesOf2 | 412.04 ms | 305.56 ms | 16.749 ms |  1.00 |     - |     - |     - |  15.42 KB |
| AllIndicesOf4 |  36.32 ms |  11.20 ms |  0.614 ms |  0.09 |     - |     - |     - |   6.18 KB |

Das trifft sich genau mit dem Verhalten vom Bug, da so dieser unperformante Framework-Code nur selten -- nur dort wo das Suchmuster tatsächlich ist -- ausgeführt wird.
Die anderen Punkte aus eingangs erwähnter Theorie zeigen sich hier doch recht deutlich.

Somit gut dass du deinen Code gepostet hast, sonst wäre dieser Bug wohl nicht entdeckt worden. Danke dafür!

Zum Prüfen der gefixten Version (per lokalem Build von .NET Core, etc.) hab ich jetzt keine Muße, aber ich gehe davon aus dass AllIndicesOf4 die schneller und weniger allozierende Variante bleibt. Der rein sequentielle Code-Pfad (Benchmark oben) dürft dann ebenfalls bessere Ergebnisse liefern.

Unabhängig vom Bug noch ein paar Anmerkungen zu den Ergebnissen / Code.

Da die Ergebnisse von isolierten Benchmarks stammen gehören diese auch entsprechend interpretiert / gelesen. V.a. in Hinblick auf Real-System ist die GC-Arbeit, durch die Allokationen, nicht zu vernachlässigen.
Ebenso wurden für die Messungen nur ein Suchmuster mit Länge 15 das viermal in die Quelle kopiert wurde verwendet. Da müssten schon ordentlich mehr Vergleiche durchgeführt werden um richtige Aussagen treffen zu können und letztlich wird es -- allgemein gesprochen -- wohl ein Kompromiss in die ein od. andere Richtung werden. Alleine schon wenn ich an ThreshouldForParallel denke. Was auf einem System besser sein mag, kann auf einem anderen System schlecht sein.

Einschub: aus den Ergebnissen ist auch zu sehen dass der Garbage Collector (GC) in .NET super Arbeit leistet. Das alloziieren ist i.d.R. nur ein Pointer-Move und daher sehr schnell. Aufwändiger ist das Abräumen (collect & compact) und hier leistet der GC -- v.a. für Gen0 -- wirklich effiziente Arbeit.

Anmerkung zu Parallel.ForEach

Parallel.ForEach versucht den ThreadPool -- sofern der TaskScheduler.Default verwendet wird -- zur Gänze auszulasten, somit kann auch das "System einfrieren".

Dies gilt es bei GUI-Anwendungen zu beachte, da so auch die GUI einfrieren kann. Hier kann mittels ParallelOptions.MaxDegreeOfParallelism eine Grenze gesetzt werden, so dass der GUI-Thread reaktiv bleibt.

Bei Server-Anwendungen, v.a. wenn viele gleichzeitige Anfragen zu erwarten sind, würde ich Parallel.ForEach eher verzichten, da die Parallelität eh über die Request behandelt wird. Ggf. sollten die RPS gemessen werden um entscheiden zu können ob der Einsatz der parallelen Schleife sinnvoll ist od. nicht.

mfG Gü

* bei diesem Ergebnis bin ich mir auch nicht ganz sicher ob der Wert stimmt. Hab zwar mehrmals den Benchmark laufen lassen und da kamen gleich Ergebnisse heraus.
Meine Zweifel konnte auch ein Profiler nicht widerlegen, da dort die CPU recht wenig Threads ausführt anstatt mit allen Kernen voll zu arbeiten. Näher hab ich das -- v.a. wegen des Bugs -- nicht untersucht. Es kann auch sein dass aufgrund der Test-Konstellation (4x Suchmuster reinkopiert) das genau mit dem Partitioner der parallen Schleife zusammenpasst. Um das zu Falsifizieren könnte ein eigener Partitioner erstellt werden, so dass in allen Varianten die parallen Schleifen gleiche Ranges erhalten. Aber auch dazu hab ich jetzt keine Lust mehr 😉

Edit: der Fix ist bereits im master-Branch.

Edit: weiteres Optimierungspotential liegt darin die "Range-Splits" direkt in der parallelen Schleife zu behandeln, so dass das nachherige sequentielle Abarbeiten entfällt, sowie das führen dieser speziellen Liste.

Speedski

20.12.2019 - 09:02 Uhr

Hallo 4dk2,

nem Sonderfall wo Datenbanken sicher zu Träge

Wenn ich diesen Thread richtig im Kopf habe wurde ja bereits mehrmals erwähnt dass der Begriff "Datenbank" sehr weit gefasst werden kann und sich nicht nur auf "klassische Datenbanken" wie SQL Server, etc. stützt. Beispielsweise gibt es auch In-Memory-Datenbanken (z.B. Redis) und die sind höchstwahrscheinlich nicht zu träge.

Bitte lass diese Pauschalierungen, v.a. wenn sie nicht allgemein stimmen. Das führt zu nichts.

mfG Gü

20.12.2019 - 08:55 Uhr

Hallo 4dk2,

Der Beitrag zu Thread.Sleep(..) ist von 26.04.2007. weiss nicht ob das noch zeitgemäß ist.

Ja ist er.
Betimmte "Dinge" werden so gut wie nie obsolet.
V.a. für Thread.Sleep gibt es mehr als genug bessere Alternativen, die einen Thread nicht einfach schlafen lassen -- dazu sollten Threads einfach nicht verwendet werden, da diese zum Arbeiten da sind.

mfG Gü

11.12.2019 - 10:28 Uhr

Wegen [Hinweis] Wie poste ich richtig? Punkt 2.2 ==> geschlossen

Crosspost.

Anmerkung: es wäre wohl für alle (v.a. für dich selbst) wesentlich einfacher und effizienter, wenn du dir die Grundlagen aneignest, anstatt jegliche Foren mit diesen Grundsatzfragen zu beglücken...

10.12.2019 - 11:56 Uhr

Hallo syl,

k-means sehe ich hier nicht passend, denn die Aufgabe hat mir Clustering nicht viel zu tun.
Das "Problem des Handelsreisenden" ist schon näher dran, v.a. mit dem von MrSparkle erwähnten weiteren Abstraktionsschritt. [Artikel] Simulated Annealing kann als Lösungsverfahren dafür verwendet werden. Bei den "Kosten", welche es ja zu minimieren gilt, baust du noch die regionalen Gruppen mit ein und das wars (zumindest konzeptionell).

mfG Gü

10.12.2019 - 11:41 Uhr

Hallo,

bei CultureInfo.InvariantCulture gibt es die Konsole besser aus, nämlich mit Infinity.

Die Zeichenfolge selbst beinhaltet die korrekte "liegende acht".

mfG Gü

06.11.2019 - 10:57 Uhr

Hallo ill_son,

ja genau so gehts (nicht nur vom Code her, sonder auch wegen deiner Recherche dazu 👍).

Mit TPL and Traditional .NET Framework Asynchronous Programming lässt sich das dann in einem Adapter schön kapseln. Ich stell mir das grob so vor:


public interface IMeasureDevice
{
    Task WaitForHardwareReadyAsync();
    Task StartSinusOutputAsync(double magnitude, double frequency);
    Task<double[,]> ReadSequenceAsync(int sampelRate, int sampleCount);
    Task StopOutputAsync();
}

public class Wobbler
{
    private const int MinPeriodCount = 5;
    private const int SampleRate     = 20;

    private readonly IMeasureDevice _measureDevice;

    public Wobbler(IMeasureDevice measureDevice)
    {
        _measureDevice = measureDevice ?? throw new ArgumentNullException(nameof(measureDevice));
    }

    public async Task<IReadOnlyCollection<FrequencyStepResult>> SweepAsync(
        double                         magnitude,
        IEnumerable<double>            frequencySteps,
        IProgress<FrequencyStepResult> progress,
        CancellationToken              ct = default)
    {
        var result = new List<FrequencyStepResult>();

        await _measureDevice.WaitForHardwareReadyAsync().ConfigureAwait(false);

        foreach (double frequency in frequencySteps)
        {
            if (ct.IsCancellationRequested)
                break;

            var frequencyResult = await this.PerformFrequencyStep(magnitude, frequency, ct).ConfigureAwait(false);
            result.Add(frequencyResult);
            progress?.Report(frequencyResult);
        }

        return result.AsReadOnly();
    }

    private async Task<FrequencyStepResult> PerformFrequencyStep(double magnitude, double frequency, CancellationToken ct)
    {
        double duration = Math.Max(1, MinPeriodCount / frequency);
        int sampleRate  = Math.Max(1000, (int)(SampleRate * frequency));
        int sampleCount = (int)Math.Ceiling(sampleRate * duration);

        await _measureDevice.StartSinusOutputAsync(magnitude, frequency).ConfigureAwait(false);
        double[,] data = await _measureDevice.ReadSequenceAsync(sampleRate, sampleCount).ConfigureAwait(false);
        await _measureDevice.StopOutputAsync().ConfigureAwait(false);

        return new FrequencyStepResult(frequency, data);
    }
}

public readonly struct FrequencyStepResult
{
    public double Frequency { get; }
    public double[,] Data   { get; }

    public FrequencyStepResult(double frequency, double[,] data)
    {
        if (frequency < 0) throw new ArgumentOutOfRangeException("negative frequency not feasable");

        this.Frequency = frequency;
        this.Data      = data ?? throw new ArgumentNullException(nameof(data));
    }
}

internal class DAQmxMeasureDeviceAdapter : IMeasureDevice
{
    public Task WaitForHardwareReadyAsync()
        => Task.Factory.FromAsync(DAQmx.BeginWaitForHardware(), DAQmx.EndWaitForHardware);

    public Task StartSinusOutputAsync(double magnitude, double frequency)
        => Task.Factory.FromAsync(DAQmx.BeginSendSignal(magnitude, frequency), DAQmx.EndSendSignal);

    public Task<double[,]> ReadSequenceAsync(int sampelRate, int sampleCount)
        => Task.Factory.FromAsync(DAQmx.BeginReadSequence(sampelRate, sampleCount), DAQmx.EndReadSequence);

    public Task StopOutputAsync()
        => Task.Factory.FromAsync(DAQmx.BeginStopOutput(), DAQmx.EndStopOutput);
}

Im Adapter DAQmxMeasureDeviceAdapter hab ich die Methoden des fiktiven Typs DAQmx nur angenommen. Diese sind durch die tatsächlichen Methoden zu ersetzen -- das schafftst du sicher.

Die Verwendung wäre dann z.B.


class Program
{
    static async Task Main(string[] args)
    {
        IMeasureDevice measureDevice                    = new DAQmxMeasureDeviceAdapter();
        var wobbler                                     = new Wobbler(measureDevice);
        var progress                                    = new Progress<FrequencyStepResult>(OnProgress);
        IReadOnlyCollection<FrequencyStepResult> result = await wobbler.SweepAsync(10, GetFrequencySteps(), progress);
    }

    private static void OnProgress(FrequencyStepResult frequencyStepResult)
    {
        // Use result
    }

    private static IEnumerable<double> GetFrequencySteps()
    {
        double currentFrequency = 10;

        for (int i = 1; i < 10; ++i)
        {
            yield return currentFrequency;
            currentFrequency *= 10;
        }
    }
}

Als Nebenbemerkung:
double[,] wenn du diesen rectangular array vermeiden kannst wäre es noch besser, denn diese sind sehr unperformant, da die CLR Methoden-Aufrufe für die Zugriffe verwenden muss. Jagged arrays werden von der CLR direkt unterstützt in Form von sz-Arrays und dort sind die Zugriffe direkte Speicherreferenzen.
Od. statt des Arrays kann auch eine eigenen Struktur verwendet werden, die dann in eine passende Collection gepackt wird.

mfG Gü

05.11.2019 - 18:28 Uhr

Hallo ill_son,

welcher Typ ist _DAQmx genau?
Ein paar Methoden von DAQmx unterstützen asynchrone Vorgänge (nach dem (alten) Asynchronous Programming Model (APM)), darauf lässt sich idealerweise aufbauen.

(Schade dass DAQmx .NET Core 3.0 nicht unterstützt, denn mit "async streams" (IAsyncEnumerable) wäre das angenehm zu lösen).){gray}

mfG Gü

05.11.2019 - 16:52 Uhr

Hallo ill_son,

nutzen Tasks den Thread Pool. Stimmt das so?

Bedingt.

Task.Run, Task.Factorry.StartNew nutzen standardmäßig den TaskScheduler.Default und der arbeitet mit dem ThreadPool.
Es kann auch ein anderer TaskScheduler verwendet werden, der mit dem ThreadPool nichts zu tun hat.
Bei StartNew hängt es auch von den Flags, die der Fabrik übergeben werden, ab ob der Task im ThreadPool landet od. ein dezidierter Thread (TaskCreationOptions.LongRunning) erstellt wird.

Task.ContinueWith, async / await können ggf. auch auf den ThreadPool verzichten, falls z.B. bei await der Task bereits fertig ist, dann wird einfach im aktuellen Thread fortgefahren. Od. bei I/O-Abschlussthreads wird der (CPU-) ThreadPool auch nicht verwendet.

Weiters lassen sich Tasks mit TaskCreationSource<T> erstellen, dabei wird ganz auf den ThreadPool verzichtet.

Und das einfachste zum Schluss: Task<int> task = Task.FromResult(42); ist ein gültiger Task, der ebenfalls ohne ThreadPool auskommt.

Hab mir deinen Link nicht angeschaut, aber ich hoffe dass auf diese Details zumindest hingewiesen wurde 😉

Das heißt für mich, ... doch besser einen eigenen Thread erstellen sollte

Ich würde es schon via Task abhandeln, nur ob der ThreadPool bzw. ein eigenständiger Thread (den der Task kapselt) verwendet wird weiß ich (noch) nicht.

verschiedene Signale erzeugt werden und jeweils eine Messung durchgeführt wird. Gesamtdauer etwa halbe bis eine Minute. Zwischendurch wären Statusmeldungen mit Zwischenergebnissen ganz nett.

Erzähl mal ein bischer mehr über den Aufbau.
Wie wird die Messung angestossen / durchgeführt?
Wie wird das Messergebnis erhalten?
Gibt die Messhardware Statusmeldungen zurück od. meinst du einfach in der UI einen Spinner, etc. anzeigen?
Wird mit einem bestimmte Protokoll (z.B. auf Basis TCP) mit der Messhardware kommuniziert?

Je nachdem gibt es u.U. elegantere Lösungen als einfach einen Task / Thread zu verwenden.

mfG Gü

05.11.2019 - 11:59 Uhr

Hallo,

falls es jemand hier produktiv einsetzt bzw. einsetzen kann, so würden mich Erfahrungen diesbezüglich interessieren.

Es ist auf jeden ein spannendes Versuchsfeld.

mfG Gü

05.11.2019 - 11:54 Uhr

Hallo ill_son,

grundsätzlich, wann Thread und wann Task?

Grundsätzlich (;-)) hatten wir diese Frage schon öfters, daher hier nur kurz der Rest bitte via (Foren-)suche.

Thread ist relativ hardwarenahe und führt einen bestimmten Code aus.
Für kurze Aufgaben (höchsten ein paar Sekunden) wird ein Thread idealerweise aus dem ThreadPool verwendet, da das Erstellen eines Threads aufwändig ist.
Für längere Aufgaben sollte der ThreadPool vermieden werden und ein eigenständiger Thread verwendet werden. Dies v.a. da der ThreadPool für solche Anforderungen nicht konzipiert wurde und somit nicht optimal arbeiten kann.

Task ist eine Abstraktion, die auf Threads aufbaut, und es dem Benutzer vereinfachen soll mit asynchronen Aufgaben zu arbeiten. Dies v.a. durch die mit C# 5 eingeführten Schlüsselwörter async / await und durch etliche API im Framework die mit Task arbeiten.

mfG Gü

04.11.2019 - 18:21 Uhr

Hallo MrSparkle,

ja stimmt, das ist an der Zeit Silverlight aus dem Titel zu streichen.

Selbst bin ich bei Desktop-GUIs nicht mehr so ganz am aktuellen Stand, daher die Frage: gibt es XAML basierte UI-Frameworks die komplett konträr zu WPF sind?

Mir gefällt der Vorschlag mit "GUI: WPF und XAML" recht gut, aber sollte es ein UI-Fx auf Basis XAML geben, da zuweit von WPF weg ist, so könnte das ein Misch-Masch werden.

mfG Gü

09.10.2019 - 13:13 Uhr

Hallo peterpan4711,

jetzt sind wir endgültig zu [Hinweis] Wie poste ich richtig? (Grundlagen) abgerutscht.

Die ArgumentOutOfRangeException bekommst du, da nicht das Ende, sondenr die Länge erwartet wird. Das sind aber wirklich absolute Grundlagen.

Mach es dir doch nicht komplizierter als es sein muss.
Probier den Code zu verstehen (wenn du schon den fertigen Code erhalten hast) und pass die zwei, drei Zeilen mit Range an, so dass es funktioniert. Es ist nicht nötig Range und Index so kompliziert nachzubauen (und wenn schon nimm den richtigen Link (siehe meinen Beitrag oben) als Vorlage und nicht irgendetwas das nicht den Code-Konventionen entspricht), da vieles aus diesen Typen eh nicht benötigt wird.
Weiters kann nur der C# 8 Kompiler die Schreibweise .. verstehen um daraus eine Range zu bilden. Das wurde alles schon erwähnt und wir drehen uns im Kreis (daher hab ich das Thema jetzt geschlossen).

Statt


ReadOnlyMemory<byte> slice = buffer.AsMemory(start..end);

schreibst du einfach


int length = end - start;
ReadOnlyMemory<byte> slice = buffer.AsMemory(start, length);

Das wars auch schon. Hättest du in die :rtfm: geguckt, so solltest du diese Lösung selbst gefunden haben.

Jetzt mach ich mir schon die Mühe Code zu erstellen (auch weil es mich interessiert hat) und um ein paar neue Features von C# 8 und .NET Core 3 zu zeigen, die auch so annotiert wurde in der Beschreibung dazu, so könntest du dir wenigstens die Mühe machen den gelieferten Code, der nicht einmal recht aufwändig und lang ist, zu verstehen um so die restlichen Probleme zu lösen. Aber stattdessen wird mit Trivial-Fehlern das Forum zur Lösungssuche erneut bemüht, anstatt es als Chance zu sehen etwas zu Recherchieren und etwas Lernen zu können...

mfG Gü

08.10.2019 - 20:49 Uhr

Hallo Th69,

ui, da war ich gleich zwei Tasten daneben 😃 Danke für den Hinweis (da ich es gerade lustig finde, editiere ich es oben nicht...)

Zum PPS: ich denke diese beiden Typen sind so trivial dass diese schon woanders vorher in C# implementiert wurden (z.B. in meine Codes, da ich das von Fortran und Matlab so gewohnt war). Mag jedoch sein, dass dies der erste explizite Artikel darüber war.

Hallo zusammen,

Range und Index sind im Grunde recht simple Typen, die wie von Th69 angemerkt recht einfach selbst implementiert werden können.
Die Verwendung von denen in .NET Core 3.0 hat jedoch den Vorteil, dass der (Ryu)JIT diese besonders behandeln kann und besseren Maschinencode erstellt. Sofern das von Bedeutung ist.

mfG Gü

08.10.2019 - 15:25 Uhr

Hallo peterpan4711,

habs vom anderen Thema abgeteilt, da es doch nicht mehr so ganz passte 😉

Falls es einen Weg gibt .NET Core sauber zu nutzen mit Forms Designer dann wäre ich auch da dabei

Die Designer-Kinder 🙂

Möglichkeiten:* die Logik (die andere Methode) in ein .NET Standard-Projekt auslagern (da gehen aber auch keine Ranges -- siehe unten, außer wird per Multi-Targeting für .NET Core 3.0 und .NET Standard 2.0 gemacht) und dann im WinForms-Projekt verwenden (so kann später zu .NET Core gewechselt werden, wenn der Designer besser wird)

  • statt WinForms WPF verwenden
  • auf den Designer verzichten und den Code selbst schreiben (mehr macht der Designer auch nicht -- guck mal in die *.Designer.cs od. wie die heißt)
  • mit einem .NET Full-WinForms-Projekt das Layout erzeugen und die Designer-Dateien im .NET Core-Projekt verwenden (am besten durch Verlinkung)
  • ...

es gibt doch viele Möglichkeiten

Ist es denn möglich System.Range mit .NET 4.8 zu nutzen?

C# 8 wird von .NET Full nicht (offiziell) unterstützt*.
Die Schreibweise start..end wandelt der C# 8-Compiler um zu


var rande = new Rande(start, end);

und verwendet dann diesen Typen für die Wahl der passenden Überladung aus.

wie ich die Klasse umschreiben kann, gerade im bezug auf die Ranges die die meisten Probleme machen

Nimm einfach die "normalen" Slice-Methoden, die Start-Index und Länge nehmen. Dabei halt die Länge vorher berechnen. Dieser Schritt entfällt nämlich durch Verwendung von Ranges, die den Code "netter" machen (falls C# 8 verwendet werden kann).
Wenn du den Code soweit verstanden hast, so sollte das kein Problem sein...und bitte nicht raten wie im letzten Beitrag, sondern :rtfm: und schauen welche Parameter benötigt werden --> das geht schneller und ist zielführender. Den Code hast du schon erhalten, daher überlass ich das jetzt dir 😉

* das meiste in C# 8 ist Syntax-Zucker, aber es gibt auch Features wie "default interfaces" die eine Änderung der Runtime nach sich zogen, daher ist nur .NET Core (Mono, etc. weiß ich nicht, gehe aber schon davon aus) darauf vorbereitet.

mfG Gü

08.10.2019 - 10:59 Uhr

Hallo peterpan4711,

Du scheinst einen echt schnellen PC zu haben:

Kann sein 😉
Bedenke auch dass nur ab .NET Core 3.0 optimale Leistung erzielt wird, da* dort "Fast-Span" verwendet wird im gegensatz zu .NET 4.x, d.h. der JIT-Compiler (RyuJit) versteht Span-Code besser und erzeugt besseren Maschinencode

  • viele Span-Operationen vektorisiert sind auf Basis von Hardware Intrinsics in .NET Core

  • sonst noch eine Menge an Optimierungen in der Code-Basis von .NET Core stattgefunden haben (und stattfinden) die nur teilweise, wenn überhaupt, nach .NET 4.x portiert werden (u.a. ist das ein Grund für das Zusammenlegen mit .NET 5, siehe .NET 5 kommt 2020)

Das "Ziel" von Hex-Editoren mit den 8..10s hast du dennoch geschlagen 😃

Ich kann in C#8 doch auch <C#8 Code und Syntax nutzen, oder?

Wie Abt schon erwähnte sind die C#-Versionen (großteils) rückwärts/abwärtskompatibel.
Ich hab ein paar neue Features von C# 8 verwendet, um zu zeigen wie diese angewandt werden können. Konkret nullable reference types und ranges.
Beides ist kein Muss für diese Methode.

Mit nullable gibt der C#-Compiler Warnungen (od. Fehler wenn TreatWarningsAsErrors gesetzt wurde) und das hilft bei der Vemeidung von [FAQ] NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt

Mit ranges lassen sich Memory-/Span-Operationen wie "Slicing" imho eleganter schreiben, wenn zwei Indizes gegeben sind, da nicht explizit die Länge ermittelt werden muss.

Wegen nullable und der praktischen Verwenden dieser Methode schaut auch die Signatur so aus:


if (source.AllIndicesOf(searchPatter, out IReadOnlyCollection<int>? indices)
{
    // Hier kann 'indices' verwendet werden und der C# 8-Compiler weiß, dass 'indices' nicht null sein darf
}

Wäre die Signatur IReadOnlyCollection<int> AllIndicesOf(...) so müsste das Ergebnis explizit in eine lokale Variable geschrieben werden, da dann auf null geprüft wird. Mit gewählter Signatur gehts in einem.

Jetzt vorsorglich der Hinweis (auch an mich selbst) nicht zuweit ins Off-Topic C# 8 abzurutschen...

mfG Gü

06.10.2019 - 19:17 Uhr

Hallo timbu42,

warum dieser super Fehler kommt, kann ich nicht sage.

Aber SetPixel ist ohnehin nicht das Wahre, schau dir z.B. GetPixel und SetPixel um Längen geschlagen. 800 mal schneller an. Konkret gehts dort darum die Bitmap zu sperren (lock bits) um dann direkt auf den Daten zu arbeiten, anstatt wie bei SetPixel jedesmal via P/Invoke ins GDI+ aufzurufen (daher ist es so langsam).

mfG Gü

06.10.2019 - 17:54 Uhr

Hallo,

meine Umsetzung wäre wie folgt:


using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;

// Könnte auch im csproj aktiviert werden
// <Nullable>enable</Nullable>
// hier aber so, da ich nur diesen Code kopiere
#nullable enable

namespace Extensions
{
    public static class ByteArrayExtensions
    {
        // Sollte empirisch ermittelt werden, mehr geht nicht auf die einfache Art und Weise
        // Könnte z.B. während der Installation durch einen Ermittlungs-Lauf auf dem Ziel-System
        // ermittelt werden, aber der Aufwand dafür steigt.
        // Der tatsächliche Wert wird vermutlich bei ein paar Millionen liegen -- ich habs nicht geprüft.
        internal const int ThreshouldForParallel = 500_000;     // internal für die Tests

        public static bool AllIndicesOf(
            this byte[]? source,
            byte[]? searchPattern,
            [NotNullWhen(true)] out IReadOnlyCollection<int>? indices)
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (searchPattern == null) throw new ArgumentNullException(nameof(searchPattern));

            if (searchPattern.Length == 0)
            {
                indices = default;
                return false;
            }

            if (source.Length < ThreshouldForParallel)
            {
                indices = new List<int>(AllIndicesOfSequential(source, searchPattern)).AsReadOnly();
                return indices.Count > 0;
            }

            var indicesSet = new HashSet<int>();
            var rangeIndices = new ConcurrentBag<int>();

            Parallel.ForEach(
                Partitioner.Create(0, source.Length),
                range =>
                {
                    rangeIndices.Add(range.Item2);

                    ReadOnlyMemory<byte> memorySlice = source.AsMemory(range.Item1..range.Item2);
                    foreach (int index in AllIndicesOfSequential(memorySlice, searchPattern))
                    {
                        lock (indicesSet)
                        {
                            indicesSet.Add(range.Item1 + index);
                        }
                    }
                }
            );

            // Alle oberen Grenzen der jeweiligen Ranges separat prüfen, um Splits über das Suchmuster zu berücksichtigen
            foreach (int range in rangeIndices)
            {
                int start = Math.Max(0, range - searchPattern.Length);
                int end = Math.Min(source.Length, range + searchPattern.Length);

                ReadOnlyMemory<byte> memorySlice = source.AsMemory(start..end);
                foreach (int index in AllIndicesOfSequential(memorySlice, searchPattern))
                {
                    indicesSet.Add(start + index);
                }
            }

            indices = indicesSet;
            return indices.Count > 0;
        }

        // Durch einen eigenen Enumerator könnte die Allokation des vom C# compiler generierten Enumerator
        // vermieden werden, aber ich denke das fällt hier nicht ins Gewicht und der Code ist so (viel) einfacher.
        private static IEnumerable<int> AllIndicesOfSequential(ReadOnlyMemory<byte> source, byte[] searchPattern)
        {
            int searchPatternLength = searchPattern.Length;
            int currentOffsetFromStart = 0;

            while (!source.IsEmpty)
            {
                int index = source.Span.IndexOf(searchPattern);

                if (index == -1)
                {
                    break;
                }

                yield return currentOffsetFromStart + index;

                source = source.Slice(index + searchPatternLength);
                currentOffsetFromStart += index + searchPatternLength;
            }
        }
    }
}

Das Test-Programm mit 900 MB und Suchmuster-Länge 15 das 4x vorkommt, braucht ~75ms um die Indizes zu finden.


init...
random data written
searchPattern copied 4 times
init done
measure...
time: 76 ms
count: 4
indices:
    0
    50
    500
    899999985

Angehängt das Projekt (mit Tests -- zumindest ein paar, die Projekt-Namen sind sinnfrei gewählt 😉).

Ich kenn jetzt die Implementierung in .NET Core für IndexOf nicht im Detail, weiß aber dass diese in mehrere Iteationen verbessert wurde. Ob dort Methoden wie von Th69 vorgeschlagen umgesetzt wurden weiß ich jetzt auch nicht, kann mir das aber schon vorstellen.

mfG Gü

06.10.2019 - 16:00 Uhr

Hallo T-Virus,

ich bastle mal eine Variante...ganz folgen kann ich deiner Beschreibung so nicht. Schauen wir einmal...

mfG Gü

06.10.2019 - 14:52 Uhr

Hallo T-Virus,

ah, danke das hatte ich überlesen und mich somit in die falsche Richtung entwickelt -- sorry (da hatte mich auch die Methoden-Signatur im ersten Post in die Irre geführt, da IReadOnlyList<int> AllIndicesOf(this byte[] source, byte[] searchPattern) passender wäre, auch der Titel sollte vom OP angepasst werden).
Ja dann einfach eine List<int> für die Suchtreffer aufbauen und den Code modifizieren dass nicht nur der erste Treffer (pro Range) zählt.
Beim Hinzufügen zur Liste bei TPL auf Races achten und entsprechend synchronisieren -- od. z.B. ConcurrentBag<int> verwenden, als threadsichere Datenstruktur.

Achtung:
Ich bemerke gerade dass die TPL-Version einen potentiellen Bug hat.
Und zwar genau dann wenn die Ranges genau so aufgeteilt werden, dass das Suchmuster auf zwei (benachbarte) Ranges aufgeteilt wird. Dann gibt es keinen Treffer.
Dieser Umstand müsste noch eingebaut werden, damit die Ergebnisse robust und sicher geliefert werden können.
(Ein Grund mehr warum Unit-Tests hilfreich wären um dieses Verhalten mit Tets abdecken zu können).

List<int> indices

Ist ein Artefakt vom Debuggen, hab ich mittlerweile oben rauseditiert.

mfG Gü

06.10.2019 - 14:30 Uhr

Hallo peterpan4711,

vllt. hab ich mich vorhin ein wenig verschätzt mit Parallel.ForEach. Wenn ich https://benchmarkdotnet.org/ verwende, so erhalte ich


|             Method |     Size |            Mean |          Error |         StdDev |          Median |  Ratio | RatioSD |
|------------------- |--------- |-----------------|----------------|----------------|-----------------|--------|---------|
| IndexOf_Sequential |      100 |        35.25 ns |      0.2245 ns |      0.2100 ns |        35.22 ns |   1.00 |    0.00 |
|   IndexOf_Parallel |      100 |     6,990.23 ns |     91.0912 ns |     85.2068 ns |     6,980.10 ns | 198.30 |    2.89 |
|                    |          |                 |                |                |                 |        |         |
| IndexOf_Sequential |   100000 |     6,652.80 ns |    126.0095 ns |    117.8694 ns |     6,638.81 ns |   1.00 |    0.00 |
|   IndexOf_Parallel |   100000 |     9,625.42 ns |     34.2770 ns |     32.0627 ns |     9,626.04 ns |   1.45 |    0.03 |
|                    |          |                 |                |                |                 |        |         |
| IndexOf_Sequential | 10000000 | 1,800,221.32 ns | 30,094.1568 ns | 26,677.6862 ns | 1,799,309.96 ns |   1.00 |    0.00 |
|   IndexOf_Parallel | 10000000 |   447,255.93 ns |  8,889.4187 ns | 20,064.8993 ns |   438,615.09 ns |   0.26 |    0.01 |

Wie erwartet ist bei kleineren Länge der Overhead der TPL zu groß und dort ist (vektorisierte) sequentielle Verarbeitung schneller.
Irgendwo in (100000, 10000000) ist dann die TPL schneller*.
Ich hab das Projekt angehängt, damit du selbst die passende Schwelle -- die je nach System unterschiedlich sein kann -- ermitteln kannst bzw. auch die anderen Varianten einfach durch Hinzufügen von weiteren Benchmark-Methoden prüfen kannst.

* zumindest im Benchmark. Wie es im Verhalten vom Gesamtsystem aussieht geht hier nicht hervor, da auch beim Benchmark die CPU voll ausgelastet wird und somit das System insgesamt kaum mehr Reserven hat -- das sollte auch berücksichtigt werden.

mfG Gü

06.10.2019 - 14:05 Uhr

Hallo peterpan4711,

auf TPL würde ich hier verzichten, stattdessen Span IndexOf verwenden. Das ist (zumindest) in der .NET Core 3.0 Version vektorisiert und wird sicher gute Ergebnisse erzielen.

Sollte dennoch die TPL (Parallel.ForEach) verwendet werden wollen, so die Überladung welche Ranges erzeugt und dann jede Range per Span IndexOf durchsuchen und im Post-Schritt das Ergebnis zusammenbauen.


public static int IndexOf(byte[] source, int startIndex, byte[] searchPattern)
    => source.AsSpan(startIndex).IndexOf(searchPattern);


using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] source = new byte[150_000];
            byte[] searchPattern = { 0xde, 0xad, 0xbe, 0xef };

            var rnd = new Random();
            rnd.NextBytes(source);

            searchPattern.AsSpan().CopyTo(source.AsSpan(75_000));

            int index = IndexOf(source, 10, searchPattern);
            bool success = index == 75_000;

            // "poor man's unit tests" ;-)
            // Richtige Unit-Tests wären besser
            Console.ForegroundColor = success ? ConsoleColor.Green : ConsoleColor.Red;
            Console.WriteLine($"actual: {index}, expected: 75000 -> {(success ? "ok" : "fail")}");
            Console.ResetColor();
        }

        // Sollte empirisch ermittelt werden, mehr geht nicht auf die einfache Art und Weise
        // Könnte z.B. während der Installation durch einen Ermittlungs-Lauf auf dem Ziel-System
        // ermittelt werden, aber der Aufwand dafür steigt.
        // Der tatsächliche Wert wird vermutlich bei ein paar Millionen liegen -- ich habs nicht geprüft.
        private const int ThreshouldForParallel = 100_000;

        public static int IndexOf(byte[] source, int startIndex, byte[] searchPattern)
        {
            if (source.Length - startIndex < ThreshouldForParallel)
            {
                return IndexOf(source.AsSpan(startIndex), searchPattern) + startIndex;
            }

            int index = int.MaxValue;

            Parallel.ForEach(
                Partitioner.Create(startIndex, source.Length),
                () => int.MaxValue,
                (range, loopState, localIndex) =>
                {
                    // Die Range-Schreibweie benötigt C#8
                    int tmp = source.AsSpan(range.Item1..range.Item2).IndexOf(searchPattern);
                    if (tmp != -1)
                    {
                        // Könnte verwendet werden, wenn nicht unbedingt das erste Auftreten gewünscht ist,
                        // sondern irgendein auftreten von searchPattern in source.
                        //loopState.Stop();

                        localIndex = tmp + range.Item1;
                    }

                    return localIndex;
                },
                localIndex => InterlockedExchangeIfSmaller(ref index, localIndex);
            );

            return index == int.MaxValue ? -1 : index;
        }

        private static int IndexOf(ReadOnlySpan<byte> source, ReadOnlySpan<byte> searchPattern)
            => source.IndexOf(searchPattern);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int InterlockedExchangeIfSmaller(ref int location, int newValue)
        {
            int snapShot;

            do
            {
                snapShot = location;
                if (snapShot < newValue) return snapShot;
            } while (snapShot != Interlocked.CompareExchange(ref location, newValue, snapShot));

            return snapShot;
        }
    }
}


mfG Gü

02.10.2019 - 14:08 Uhr

Hallo AceTecNic,

Du gibst nicht zufällig "Onlinekurse" oder ähnliches?

Nein.

mfG Gü

02.10.2019 - 10:01 Uhr

Hallo AceTecNic,

Bei mir sind "Programme" immer nur auf einer Seite... Zumindest noch.

Das ist / war vermutlich bei jedem zu Beginn so. Auch daher das Beispiel mit einem Aufbau wie es "besser", zumindest aufgeteiltert, geht.
Alle Typen haben recht wenig Code, da eben alles aufgeteilt ist. Nicht nur die Übersichtlich-/Lesbarkeit vom Code steigt dadurch, auch lässt sich der Code besser warten. D.h. das Projekt ist für spätere Änderungen / Erweiterungen / Anpassungen besser gerüstet als wenn alles in einer Mega-Datei (Form1.cs) steht.
Zusätzlich wird somit der Code testbar, einfacher versionierbar usw. Die Vorteile davon sind mannigfaltig, ich werde nicht alles wiederholen, da diese mehr od. weniger gesichertes Wissen ist 😉

Da das schon ziemlich komplex ist

Ich wünsche dir dass du nach dem "Aha-Erlebnis" das nicht mehr als komplex, sondern trivial siehst. (Das war zumindest meine Motivation das Projekt so zu erstellen)

im msgbox ausgibt.

Als Hinweis für später: Warten auf Schließen einer anderen Form [und warum man Dialoge nicht modal machen sollte] und Nenne deinen Fall, wo du denkst, ohne modalen Dialog geht es nicht, und ich nenne eine Alternative

mfG Gü

01.10.2019 - 21:02 Uhr

Hallo AceTecNic,

Habe ein Bild mit angefügt wie ich das machen möchte.

Genau mit so einem Bild solltest du die Überlegungen starten, noch bevor eine Zeile programmiert wird.
Das gibt genau das wieder was du haben willst. Die Umsetzung davon ist dann Programmierer-Handwerk.

Ich hoffe du bist nicht so lange dran gesessen extra wegen mir

Ich schick dir dann die Rechnung 😉
Hat nicht so lange gedauert, aber ich wollte ein paar Punkte wie Unit-Tests, etc. einbauen. V.a. damit ein "Anfänger" einen möglichen Weg sieht wie es gehen kann. Unit-Tests haben oft eine hohe moralische Hemmschwelle, die jedoch unbegründet ist. Ganz im Gegenteil: ohne Tests weiß ja nicht ob der Code überhaupt passt.

andere Codes gelesen habe und mir durchblick verschafft hab.

Versuch einmal das Projekt -- v.a. die Model-Typen -- zu verstehen.
Gem. deinem Bild sollte das XML wie von Abt im anderen Thread skizziert ausschauen. Wenn du das Projekt hier verstanden hast, so ist es ziemlich einfach* das so zu ändern, damit es wie in deinem Bild gezeigt funktioniert.

* das mit "einfach" bitte nicht falsch verstehen. Ich bin schon lange dabei, daher ist es einfach 😉 Mit der obigen Ansage will ich nur ausdrücken, dass du das sicher schaffen wirst, sobald du verstanden hast worum es geht, denn es ist keine Hexerei, etc. nötig. Einfach die Typen etwas umbauen und fertig. Der erste Schritt ist aber das Verständnis was passiert. Dazu zählt auch -- wie du richtig erkannt hast -- das Dictionary als praktische Datenstruktur.

mfG Gü

01.10.2019 - 20:45 Uhr

Hallo,

wegen Crosspost mach ich hier zu.

Ergänzend zu Abts Hinweisen, sollten auch [Hinweis] Wie poste ich richtig? Punkt 2.2 beachtet werden -- aus dort genannten Gründen.

mfG Gü

01.10.2019 - 01:14 Uhr

Hallo AceTecNic,

Ich möchte keinen Code ergaunern

Ich hab das Gefühl dass das auch stimmt, daher hab ich ein kleines Projekt gebastelt, andem du dich orientieren kannst. Siehe Anhang.
VS 2017 od. neuer mit installierten .NET 4.8 ist nötig (kann sicher auch auf .NET 4.5 laufen).

Was ist dabei?* Trennung von Logik und UI (nähere Infos -> [Artikel] MVVM und DataBinding (ist für WPF, aber die Grundlagen gelten auch für WinForms, etc.)

  • Unit-Tests (für ein paar Typen, siehe ReadMe.txt im Test-Projekt)
  • ein paar C#-Features, damit du siehst wie es elegant möglich ist

Was ist nicht dabei?* deine gewünschte Anwendung 😉 Das hier soll eine Hilfestellung sein, wie von dir gewünscht

  • Unit-Tests für alle Typen
  • komplette UI mit Hinzufügen, Editieren (dazu reichen meine Winforms-Kenntnisse auch nicht mehr aus -- müsste mich selbst wieder einarbeiten)

Den Rest solltest du selbst schaffen können.

Noch etwas: in Speichern in eine bestehende txt-Datei hat Abt ein mögliches korrektes XML gezeigt, dessen Aufbau genau umgekehrt zu meinem hier ist. Keines der beiden erachte ich als "richtiger", es hängt vielmehr vom Anwendungsfall ab. Also ob der Lagerort (bei mir) od. die Farbe (bei Abt) im Vordergrund steht. Filtern lässt sich -- z.B. mit Linq -- in beide Richtungen.
Hier ist auch ein Vorteil einer richtigen DB zu sehen, nämlich dass diese Unterscheidung egal ist, da DBs für so etwas ausgelegt und gemacht wurden.

Letzter Hinweis zum Projekt: ich hab das jetzt eben schnell runtergetippt, daher erhebe ich auch keinen Anspruch dass es ausgereift und ideal ist. Es gibt durchaus Verbesserungen, aber für eine grobe Richtschnur sollte es reichen.

mfG Gü

30.09.2019 - 21:03 Uhr

Hallo AceTecNic,

Ist das ganze einfacher mit einer Datenbank?

Das hatten wir schon in Speichern in eine bestehende txt-Datei und braucht hier nicht wiederholt werden.

Wenn du es per XmlSerialisierung angehst, so verwende im Speicher als Datenklasse ein Dictionary<K, V> mit entsprechendem Key (Lagerort, RAL). Dann wird es einfacher.

Es ist auch nicht nötig XElement als Modell zu verwenden, das ist schon zu nah am XML dran. Entweder per XElement selbst das aufbauen od. Xml-Serialisierung. Einfacher ist es aber mit dem Dictionary.

Überleg dir generell was du abbilden willst. Wie das geschehen soll lass vorerst außer Acht.
Du willst eine Übersicht haben, die angibt wieviel von der jeweiligen Farbe an welchem Lagerort vorhanden ist. Korrekt?

D.h. die erste Unterteilung ist nach Lagerort. Jeder Lagerort kann mehrere Farben haben und zu jeder Farbe soll die vorhanden Menge gespeichert werden.
Als (UML-) Modell schaut das dann wie im angehängten Bild aus.

Das gilt es nun in Code zu gießen...(update kommt gleich)

mfG Gü

26.09.2019 - 15:56 Uhr

Hallo,

Suchfunktion mit einbinden könnte ich z.b. nach RAL5018 suchen und bekäme die Werte darunter mitgeliefert?

Ja. Von der Suche bekommst du das Element und damit verbunden sind alle Kinder.
Vorausgestezt unter Suche verstehen wir Linq2Xml, XPath, etc. und keine einfaches string.IndexOf.

  
<Gewicht Type="Kg">14.5</Gewicht>  
  

Ansonsten wie der Lehrer

Wenn schon dann "Masse" und die SI-Präfix "k" klein. SCNR 😃

mfG Gü

26.09.2019 - 12:53 Uhr

Hallo Anna85,

gibts zur XML-Datei auch ein XML-Schema? Dann hast du die nötige Struktur schon bzw. kannst du dir via Schema ein Beispiel-XML genieren lassen das übersichtlich ist.

mfG Gü

26.09.2019 - 12:49 Uhr

Hallo AceTecNic,

Ich kann doch die Listbox direkt mit einer Datenbank verbinden?

Ja ist möglich, aber auf lange Sicht nicht ganz ideal, da es eine direkte Kopplung von der UI (ListBox) zur DB ist. Besser wäre eigene Modell-Klassen zu erzeugen, die per einem Service geladen werden und diese Modell-Instanzen dann an das UI gebunden werden.
So hast du es später leichter, falls es Änderungen gibt. Zudem ist der Code so testbarer (Unit-Tests, etc.).
Wenns eine WPF Anwendung ist, so siehe [Artikel] MVVM und DataBinding

60 Positionen mit jeweils 3 Angaben habe

Gut für so geringe Datenmengen ist SQL Server vllt. etwas überdimensioniert.

Da kannst du schon XML nehmen und per Deerialisierung in den Speicher laden um dort die Manipulationen vornehmen, dann per Serialisierung wieder ins Dateisystem schreiben.
Wegen FTP: beachte aber dass es bei diesem Vorgehen keinen (echten) Schutz vor Mehrbenutzerzugriffen / Races gibt. D.h. die Änderung die Benutzer A macht, können von Benutzer B leicht überschrieben werden. "Echte" DBs bieten hier Möglichkeiten an das handzuhaben.
Stellt das kein Problem dar, so nimm einfach XML.

mfG Gü

26.09.2019 - 10:48 Uhr

Hallo DerDicke69,

schau z.B. How to Track Who Accesses, Reads Files on your Windows File Servers (ist zwar Werbung für ein Produkt, aber die Vorgehensweise wir gezeigt).

Die Ereignisanzeige kannst du dann auch auslesen mit C#.

mfG Gü

26.09.2019 - 10:45 Uhr

Hallo AceTecNic,

Werd das wohl über die XML machen und Dateiaustausch mittels FTP.

Machs lieber ordentlich und nimm eine Datenbank.

Welches Problem hattest du mit dem SQL Server? Firewall Port TCP 1433?

mfG Gü

25.09.2019 - 16:07 Uhr

Hallo Saftsack,

"Interfaces" Punkte, wo Objekte in einer Beziehung mit ihrer Umgebung stehen.

Das passt auch und ist zu verwechseln mit dem C# Schlüsselwort interface.
Auch eine abstrakte Klasse kann ein Interface sein, genauso wie ein interface od. eine Methode -- je nachdem wie es betrachtet wird.
Lass dich davon nicht beirren.

Irgendwo brauchst du auch eine konkrete Ausprägung eines Interfaces. Schau dir dazu Abts Beispiel an.

mfG Gü

25.09.2019 - 11:03 Uhr

Hallo DerDicke69,

um welcher Betriebssystem handelt es sich? Je nachdem wird auch die Antwort unterschiedlich ausfallen.

mfG Gü

24.09.2019 - 20:06 Uhr

Hallo Saftsack,

deine Herangehensweise ist auch vernüftig. Dennoch tue ich mir schwer eine Antwort zu verfassen, wie du sie vllt. erwartest. Bzgl. Patterns ist es oft auch schwierig eine pauschale Antwort zu geben, wie "nimm eine Factory, falls...". Um so etwas zu beurteilen, müsste das größere Bild der zu erstellenden Anwendung / Komponente bekannt sein.

Schau dir dazu die ersten Treffer von Google-Suche nach c# design patterns an. Da gibt es ein paar ganz gute Erklärungen mit Beispielen.

Letztlich ergeben sich solche Entscheidungen von alleine wenn die Erfahrung wächst. Da gehört es auch dazu, sich ein paar mal zu verennen -- aber aus Fehlern lernt man (hoffentlich).

Zum Lernen von Klassenhierarchien ist das oben angehängte Klassendiagramm schon brauchbar, für das Lernen von C# und Design Pattern kommt es mir nicht so ideal vor.
Orientiere dich da eher an [FAQ] Wie finde ich den Einstieg in C#? und mach einfache Beispiel-Programme, die dann schrittweise mit den Pattern ergänzt werden. Schau dir da auch unbedingt automatisierte Tests (v.a. Unit-Tests) an, denn durch die Testbarkeit machen manche Pattern mehr sinn, da sonst eben nicht getestet werden kann.

mfG Gü

24.09.2019 - 18:30 Uhr

Hallo Abt,

kein Entladen von Assemblies brauchst (was es in der Form auch in .NET Core/.NET 5 nicht mehr gibt

mit den AppDomains hast du recht. Entladen von Assemblies geht ab .NET Core 3.0 wieder und zwar mit Hilfe vom AssemblyLoadContext (ein Beispiel ist hier).

mfG Gü

24.09.2019 - 18:23 Uhr

Hallo Saftsack,

wo hängst du denn?

Ich tu mir schwer eine andere Antwort zu schreiben, da das Klassendiagramm vorliegt und es für mich in Code zu gießen nicht schwer ist.
Den Code einfach posten bringt dir nicht viel -- zumindest hast du wenig Lerneffekt dabei.

Schau dir dazu auch UML-Klassendiagramm an, vllt. ergibt sich dann schon ein besseres Bild für dich.

mfG Gü

24.09.2019 - 14:23 Uhr

Hallo HeikoAdams,

siehe Abts Antwort.

Der The .NET Portability Analyzer wird wahrscheinlich weiter ausgebaut werden.

Klassenbibliothenken, sofern sie keine Abhängigkeiten zu [Tutorial] Konfigurationsmodell im .NET Framework, etc. haben und das sollten sie auch nicht -- sondern per Abstraktionen, könnten relativ einfach zu .NET Standard 2.0 (ev. auch .NET Standard 2.1, aber das wird von .NET 4.x nicht unterstützt) umgewandelt werden*.

Für Anwendungen selbst wird u.U. mehr Aufwand nötig sein.
Aber .NET 4.x wird in Windows enthalten bleiben, genauso wie .NET 2 nach wie vor gut 15 Jahre nach Einführung noch enthalten ist.
Von daher können bestehende Projekte ohne Panik weiterhin verwendet werden. Es können auch nur Teile nach .NET 5 umgewandelt werden -- ein "alles od. nichts" muss es nicht sein. Klar setzt die eine gewisse Architektur voraus bzw. ist jetzt an der Zeit eine suboptimale Architektur durch eine bessere zu ersetzen -- nicht umsonst wird das hier im Forum schon lange gepredigt 😉

* meine Vorgehensweise ist meist wie folgt1. bisheriges Projekt eine temp-Namen geben

  1. neues Projekt (mit "neuem" MsBuild-Format) erstellen
  2. Koperien der Dateien vom temp-Projekt zum Neuen
  3. allfällige Compiler-Fehler beheben
  4. temp-Projekt (sollte so gut wie leer sein) löschen

mfG Gü

24.09.2019 - 13:31 Uhr

Hallo Abt,

MS hat das v.a. zu Beginn von .NET Core so kommuniziert, mittlerweile wurde viel gelernt und somit hat sich das auch ein wenig (tw. viel) geändert.

In Bezug auf GAC gibt es mittlerweile mit den "packs" und "shared frameworks" auch so etwas wie eine moderen Reinkarantion, da es sich einfach als vorteilhafter herausgestellt hat.
Der Weg, dass per NuGet "pay for what you play" gemacht wird ist eher obsolet.

Insgesamt empfinde ich .NET (samt Ökosystem) unter der dotnet-Foundation auf einem gutem Weg. Es ist auch schön zu sehen wie mit der Zeit Erkenntnisse gewonnen und umgesetzt werden. Somit ist .NET Core 3.0 schon wesentlich moderner als .NET Core 1.0 😃

mfG Gü

24.09.2019 - 08:46 Uhr

Hallo,

als Ergänzung vom Eingangs-Post:

.NET 5 wird Ende 2020 kommen.

Dazwischen wird es noch .NET Core 3.1 (samt ASP.NET Core 3.1, EF Core 3.1) geben, das für November 2019 geplant ist.

.NET Core 3.1 wird dann eine LTS (long term support) Release sein.

mfG Gü

24.09.2019 - 08:45 Uhr

Hallo,

mir gefallen die Pläne für die Zukunft von .NET.

die Abstand vom monolitischen Gedanken von .NET Framework genommen hat

Wie äußert sich das?
Indem es Ideen gibt etliche git-Repos der dotnet-Foundation zu einem großen Mono-Repo zusammenzuführen?
Indem es statt dem GAC halt "Frameworks", "Runtime stores", etc. gibt?
Indem der Weg "NuGet as delivery vehicle" schon wieder großteils verlassen wurde und auch einst separate Projekt zusammengefasst werden (damit auf Interna zugegriffen werden kann)?

Der Begriff Monolith ist momentan so etwas wie ein Unwort und daher muss es omnipräsent erwähnt werden, dass es kein Monolith ist. Das hat schon inflationäre Züge und ist teilweise am Ziel vorbei, denn für ein Framework wie .NET mit all seinem Komponenten wie EE, JIT, GC, BCL, ... macht ein Monolith durchaus Sinn, da einzelen Komponenten ohne die anderen nicht funktionieren. Daher wird .NET 5 auch ein Monolith sein, so wie es aktuell auch .NET Core 3.0 ist. Da lässt sich nichts schönreden.

In .NET Core gibt es mehr Abstraktionen als im klassischen .NET Framework, das macht jedoch aus einem Produkt noch lange keine "Nicht-Monolithen".
Von der Architektur her ist sich .NET Core und .NET Full gar nicht so unähnlich.
In Hinblick .NET 5 (wie der momentane "Arbeitstitel" lautet) wird lediglich geschaut dass mehr Ressourcen zwischen .NET, Mono (Xamarin), etc. gemeinsam genutzt werden können (und wie die Repos vereinheitlicht werden können).

Bevor jetzt eine Diskussion entbrannt, sollte der Begriff Monolith jedoch definiert werden, dnen das bin ich hier (absichtlich) auch schuldig geblieben, da ich genauso vom üblichen Verständnis ausgehe wie die eingangs zitierte Aussage 😉

PS: In Introducing .NET 5 hat Rich Lander keine Silbe über einen Monolithen verloren (und das hat er sicherlich nicht einfach vergessen zu erwähnen 😉).

PPS: Ich bin nicht für od. gegen Monolithen, sondern dafür wo es sinnvoll ist und dagegen wo es keine Sinn macht. Also per se sind Monolithen nicht schlecht, es kommt nur darauf wo und wie sie eingesetzt werden. Es gibt Anwendungen / Architekturen da sind Monolithen eher nicht geeignet (-> Mikroservices sind da eine Interessante Alternative).
Ich bin jedoch dagegen, dass ein Begriff einfach als "böse" / "schlecht" abgestempelt wird wenn diese nicht der Fall ist.

mfG Gü

23.09.2019 - 18:35 Uhr

Hallo Saftsack,

sinnvoll ist das meist nicht -- wie Abt schon sagte.
Sowas kenn ich in der praktischen Verwendung nur als Optimierung von Caches, wenn i.d.R. nur ein Element gecached werden soll.

Anhand deiner Klassen könnte das z.B. so ausschauen:


public class Foo
{
    private ProductConnectionPoint _first;
    private List<ProductConnectionPoint> _points = new List<ProductConnectionPoint>();

    public void Add(ProductConnectionPoint point)
    {
        if (_first == null)
        {
            _first = point;
        }
        else
        {
            _points.Add(point);
        }
    }

    public IEnumerable<ProductConnectionPoint> GetPoints()
    {
        if (_first == null) yield break;

        yield return _first;

        foreach (ProductConnectionPoint point in _points)
            yield return point;
    }
}

public class ProductConnectionPoint { }

mfG Gü

23.09.2019 - 08:21 Uhr

Hallo bredator,

Aber zum Jahreswechsel dann.

Da steht dann schon bald .NET 5 vor der Tür 😉

mfG Gü

12.09.2019 - 13:34 Uhr

Hallo,

nicht die Speicherallokation teuer

Das ist als generelle Antwort / Hinweis zu sehen.

Speicherallokationen auf dem Heap (per new od. implizit wie beim Kopieren vom String) sind in .NET nicht aufwändig. Im Grunde wird vom GC nur ein Pointer verschoben und der so allozierte Speicher genullt (aufgrund von ".NET safety"). Das geht schnell.
Aufwändig hingegen ist das Abräumen vom nicht mehr benötigten Speicher durch den GC -- das sammeln und kompaktieren vom Speichern und ev. das verschieben der Objekte in die nächste Generation.
D.h. wenn Speicherallokaitonen vermieden werden können, so erspart man dem GC eine Menge Arbeit und die Anwendung wird insgesamt schneller. Bemerkbar vllt. nicht unbedingt bei Konsolenanwendungen die nur ein Job ausführen und sich beenden, sondern v.a. bei Server-Anwendungen u.ä. Anwendungen die "lange" laufen.

Speicherallokationen können oft vermieden werden durch* geschicktere Algorithmen / geschicktere Verwendung von BCL-Typen

  • Verwendung vom ArrayPool<T>, mit dem sich die Speicherallokationen amortisieren lassen
    Alternativ auch MemoryPool<T>, etc. Generell durch Poolen von Objekten die häufig verwendet werden

  • Stack-Allokationen mittels stackalloc -- vorzugsweise in Verbindung mit Span<T>, denn hier ist das Allozieren und Deallozieren vom Speichern nur eine Pointer-Operation (und das Nullen vom Allozierten Speicher), d.h. sehr schnell

mfG Gü