Wahl der "richtigen" Programmiersprache für ein Audio-Projekt
-
Ähem? schrieb:
Keinst ernstzunehmender Experte behauptet, dass Java schneller sei als C++.
Das habe ich auch nicht behauptet, aber Du kannst das Thema gerne mal
im nächsten Java-Forum postenÄhem? schrieb:
Qutasch. Assembler ist deutlich simpler als C++. C++ gehört mit zu den kompliziertesten Sprachen überhaupt.
Wirklich? Dann schreib doch mal mit Assembler, sagen wir, einen std::vector, wie es ihn bei C++ gibt. Oder z.B. eine Klasse.
Ich behaupte, daß das mit C++ wesentlich leichter zu lernen ist als mit Assembler.Mag sein, daß der Grundwortschatz von Assembler wesentlich geringer ist als der von C++, bloß was nützt das, wenn die Komplexität binnen kürzester Zeit so dermaßen ansteigt, daß nur noch absolute Profis den Überblick behalten.
-
ich glaube ihr redet leicht aneinander vorbei.
assembler: einfach, aber es ist schwieriger damit etwas komplexes zu realisieren
c++: komplex, dafuer sind komplexe dinge einfacher damit zu realisierenist oft so, einfache tools beduerfen mehr schweissarbeit.
@GoaZwerg
solange das nur fuer dich ist und du eine halbwegs moderne cpu + genug ram hast, benutzt einfach das womit du am besten zurecht kommst. selbst wenn c# in manchen dingen 10mal langsammer ist, kannst du mit c++ auf einem normalen pc mit 100fps ein H264 video encoden, das sind datenmengen bzw. berechnungen bei denen du vermutlich nur noch rauschen hoeren wuerdest, wenn dein sound mixer/generator sie verarbeiten/generieren wuerde. es gibt schon auf dem ipad rebirth http://www.youtube.com/watch?v=zomsMea6KxMalso achte nur auf deine produktivitaet/vorlieben.
-
GoaZwerg schrieb:
Aber nun stehe ich einfach vor der Wahl der "richtigen" Programmiersprache für dieses Projekt.
faust (=functional audio stream)
-
faust (=functional audio stream)
Das kannte ich noch nicht, sieht auf den ersten Blick ziemlich interessant aus! Danke für den Tipp.
-
Ist doch irgendwie gar nicht schwer, c mit demm gcc auf Linux. Und Linux asm und Hardwarezugriffe sind auch nicht so übel im Gegensatz zu Windows. Und Charli Petzolds wichtige Windowsbücher bauten auf C auf. Eins nach dem anderen, würde ich sagen, c und java ausreizen können, dann C++. Das ist aber ein bißchen doof, weil java eine eigene Api hat. Aber mit c und asm und den richtigen Bibliotheken kommt man auch gut über die ersten Runden. Windows-Assembler ist ein eigenes Thema, man braucht eigentlich erstmal gute Winapi Grundlagen. Ich finde C drängt sich da irgendwie total auf.
-
Heute Abend hab ich mal Zeit gefunden ein paar weitere Tests zu machen.
Jetzt bin ich doch etwas verwirrt
Ich habe einfach mal stumpf getestet wieviele Int32-Inkrements ich in ca. 1000ms hinbekomme, auf einem PIC32MX Controller getaktet mit 80 MHz, einem Parallax-Propeller mit 8 COGs, sowie auf meinem Laptop (2,4 GHz, Duo-Core mit Hibernate für 4 virtuelle Kerne, aber getestet hab ich natürlich nur 1 Core) in C++ Native VS2010, C++ Native MinGW, C++ CLR, C# und VB.NET. Daneben hab ich noch nen DSPIC auf 16 Bit getestet, der kommt bei höchster empfohlener PLL-Konfiguration auf 40 MIPS @ 16 Bit (wegen der 16 Bit fällt der aber aus dem Vergleich raus).
Getestet habe ich hierbei nur erstmal Fixed-Point.
Auf dem PIC32 erreiche ich, in Assembler, wie erwartet zuverlässig die 80 MIPS (ohne Loops, bzw. mit Hardware-Loops, darum hinkt der Vergleich mit dem PC sowieso etwas, die Werte vom 32MX sind also eher theoretischer Natur), die Werte auf dem Parallax konnte ich nicht vernünftig messen, aber da er eh keinen Hardware-Multiplizierer bzw. keine FPU und keinen DSP hat und viel zuwenig Speicher ist der eh sowieso schwer zu vergleichen.
Mit der Stopwatch und optimiertem Release-Built und mit Schleife erreiche ich in C#, CLR C++ und VB.NET nur 13 MIPS (komischerweise war VB.NET in mehreren Fällen ein klein wenig schneller als Unsafe-C# und CLR C++), im Debug-Mode in allen Fällen sogar nur 6 MIPS (!) im Mittel.
Dann habe ich den C# und VB.NET-Code so umgestellt das er mit Threading Schleifenlos arbeitet, da komme ich auf ca. 106 MIPS. Aber auf meiner ursprünglichen Platine werkeln 3 Stück von den PIC32 in Serie, was somit 4,5 DMIPS ergibt (effektiv, zumindest nach meinen Versuchen, 240 MIPS @ 32 Bit).
Der VS2010 C++ kommt, mit Schleife, aber ohne Threading, auf ca. 56 MIPS und der GCC lag im Mittel stets 10 MIPS darunter. Mangels Erfahrung in der Programmiersprache C++ konnte ich es dort nicht mit Threading probieren.
Natürlich is so ein Benchmark nicht wirklich zu legitim, aber ich wollte ja auch nur mal einen groben Vergleich haben.
Was mich jetzt daran irritiert: der PIC32 hat 1.5DMIPS, ein Laptop hat doch zig-mal mehr und zudem ne FPU?!
Klar, C# mit Unsafe-Code und Optimierung kommt auf über 100 MIPS, aber das ist doch nicht wirklich viel mehr als so nen Popel-32MX?
Eigentlich hätte ich nen Faktor von 1000 oder mehr für C# erwartet?!
*Verwirrt bin*
-
Das erinnert mich hieran:
http://www.bernd-leitenberger.de/benchmark.shtmlUnd Intel hat natürlich ein gutes Marketing
Und auf dem Rechner hat man auch noch ganz andere Dinge nebenbei mitlaufen.
-
Nur mal so aus Interesse.. wie hast du denn die Integerincrements gemessen, ohne dass das komplett wegoptimiert wurde?
-
Muss mich erstmal korrigieren, hatte was von Schleifenlos geschrieben, diese Formulierung war Murks.
@cooky: wenn man das Ergebnis ausgibt wird es nicht wegoptimiert. Verwendet man die Variable allerdings hinterher nicht mehr dann scheint der IL-Compiler das zu merken und optimiert es dann einfach weg, meines Wissens nach.
-
Könntest du mal deinen Testcode für C++ und C# posten?
-
Ich habe jetzt mal ein etwas erweitertes Testprogramm geschrieben, dass dürfte dann ausagekräftiger sein. Hier die C# Version:
using System; using System.Diagnostics; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { const int sampleRate = 44100; const double leaky = 0.99; double position = 0; double delta = 1; double max = 0; double posSinc = 0; double dcOffset = 0; double @out = 0; Stopwatch sw = new Stopwatch(); // Impulse-Train Sweep Test sw.Start(); for (int frequency = 50; frequency <= Convert.ToInt32(sampleRate * 0.5); frequency++) { max = 0.5 * sampleRate / frequency; dcOffset = -0.49 / max; position += delta; if (position < 0) { position = -position; delta = -delta; } else if ((position > max)) { position = max + max - position; delta = -delta; } posSinc = Math.PI * position; if (posSinc < 1E-05) { posSinc = 1E-05; } @out = leaky * @out + dcOffset + Math.Sin(posSinc) / posSinc; } sw.Stop(); Console.WriteLine(string.Format("Dummy-Out: {0}", @out)); Console.WriteLine(string.Format("{0}µs", sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L)))); Console.ReadKey(); } } }
-
Und hier dasselbe (hoffentlich) in C++ zusammengefrickelt (VS 2010):
#include "stdafx.h" #include <math.h> #include <iostream> using namespace std; #include "windows.h" int _tmain(int argc, _TCHAR* argv[]) { const int sampleRate = 44100; const double leaky = 0.99; double position = 0; double delta = 1; double max = 0; double posSinc = 0; double dcOffset = 0; double out = 0; // Impulse-Train Sweep Test LONGLONG g_Frequency, g_FirstNullCount, g_LastNullCount, g_FirstCount, g_LastCount; if (!QueryPerformanceFrequency((LARGE_INTEGER*)&g_Frequency)) printf("Performance Counter nicht vorhanden"); double resolution = 1000000 / ((double)g_Frequency); printf("Frequenz des Counters: %lld kHz\n", g_Frequency/1000); //lld -> LONGLONG darstellung printf("Dadurch maximale Aufloesung: %4.5f us\n", resolution); //null-messung QueryPerformanceCounter((LARGE_INTEGER*)&g_FirstNullCount); QueryPerformanceCounter((LARGE_INTEGER*)&g_LastNullCount); double nulltime = (((double)(g_LastNullCount-g_FirstNullCount))/((double)g_Frequency)); printf("Null-Zeit: %4.5f us\n", nulltime * 1000000); //beginn messung QueryPerformanceCounter((LARGE_INTEGER*)&g_FirstCount); for (int frequency = 50; frequency <= sampleRate * 0.5; frequency++) { max = 0.5 * sampleRate / frequency; dcOffset = -0.49 / max; position += delta; if (position < 0) { position = -position; delta = -delta; } else if ((position > max)) { position = max + max - position; delta = -delta; } posSinc = 3.14159265358979323846 * position; if (posSinc < 1E-05) { posSinc = 1E-05; } out = leaky * out + dcOffset + sin(posSinc) / posSinc; } //2. Messung QueryPerformanceCounter((LARGE_INTEGER*)&g_LastCount); double dTimeDiff = (((double)(g_LastCount-g_FirstCount))/((double)g_Frequency)); //Von der gemessenen Zeit die "Null-Zeit" abziehen, um genauer zu werden double time = (dTimeDiff - nulltime) * 1000000; //mikro-sekunden printf("Zeit: %4.5fus\n" ,time); printf("Dummy-Out: %4.5f\n", out); int get; cin >> get; return 0; }
-
Bei diesem Vergleich sieht es auf meinem Laptop schon anders aus.
Die C# Version braucht durchschnittlich 1412 Mikrosekunden.
Die Ergebnisse der C++ Version schwanken sehr stark zwischen 950 und 1420 Mikrosekunden (offenbar ist die Messmethode sehr ungenau).
-
Brauchst du wirklich einen Performancecounter? Normalerweise sollte die Messung schon so ~2 Sekunden dauern, ansonsten schwankt das zu stark. Kannst du das so umbauen, dass es länger dauert?
Edit: Wenn du "sampleRate * 0.5" durch "sampleRate * 500" ersetzt, dauert es ca. 2 Sekunden. Damit kannst du dann auch vernünftig messen. Nur ist natürlich klar, dass hier eigentlich nur die Geschwindigkeit der sin() Funktion gemessen wird.#include <cmath> #include <ctime> #include <iostream> int main() { const int sampleRate = 44100; const double leaky = 0.99; double position = 0; double delta = 1; double max = 0; double posSinc = 0; double dcOffset = 0; double out = 0; auto t = std::clock(); for (int frequency = 50; frequency <= sampleRate * 0.5; frequency++) { max = 0.5 * sampleRate / frequency; dcOffset = -0.49 / max; position += delta; if (position < 0) { position = -position; delta = -delta; } else if ((position > max)) { position = max + max - position; delta = -delta; } posSinc = 3.14159265358979323846 * position; if (posSinc < 1E-05) { posSinc = 1E-05; } out = leaky * out + dcOffset + sin(posSinc) / posSinc; } auto used_time = (std::clock() - t) / static_cast<double>(CLOCKS_PER_SEC); std::cout << "Time: " << used_time << "\tOut: " << out; std::cin.get(); }
-
Naja, keine Ahnung wegen dem Performance-Counter, ich hab ja von C++ Null Ahnung
Zum testen braucht die Funktion ja keinen wirklichen Sinn ergeben, daher hab ich jetzt mal die Range von 1 Hz bis 5000 * Sample-Rate erweitert und die Sinus-Funktion rausgeworfen.
Hier der C# Code:
using System; using System.Diagnostics; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { const int sampleRate = 44100; const double leaky = 0.99; double position = 0; double delta = 1; double max = 0; double posSinc = 0; double dcOffset = 0; double @out = 0; Stopwatch sw = new Stopwatch(); sw.Start(); for (int frequency = 1; frequency <= Convert.ToInt32(sampleRate * 5000); frequency++) { max = 0.5 * sampleRate / frequency; dcOffset = -0.49 / max; position += delta; if (position < 0) { position = -position; delta = -delta; } else if ((position > max)) { position = max + max - position; delta = -delta; } posSinc = Math.PI * position; if (posSinc < 1E-05) { posSinc = 1E-05; } @out = leaky * @out + dcOffset / posSinc; } sw.Stop(); Console.WriteLine(string.Format("Dummy-Out: {0}", @out)); Console.WriteLine(string.Format("{0}µs", sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L)))); Console.ReadKey(); } } }
Und hier Dein angepasster Code (womit kriege ich das kompiliert? MinGW meckert das er ctime nicht kennt):
#include <cmath> #include <ctime> #include <iostream> int main() { const int sampleRate = 44100; const double leaky = 0.99; double position = 0; double delta = 1; double max = 0; double posSinc = 0; double dcOffset = 0; double out = 0; auto t = std::clock(); for (int frequency = 1; frequency <= sampleRate * 5000; frequency++) { max = 0.5 * sampleRate / frequency; dcOffset = -0.49 / max; position += delta; if (position < 0) { position = -position; delta = -delta; } else if ((position > max)) { position = max + max - position; delta = -delta; } posSinc = 3.14159265358979323846 * position; if (posSinc < 1E-05) { posSinc = 1E-05; } out = leaky * out + dcOffset / posSinc; } auto used_time = (std::clock() - t) / static_cast<double>(CLOCKS_PER_SEC); std::cout << "Time: " << used_time << "\tOut: " << out; std::cin.get(); }
-
Ich empfehle dir C++ für das Audio Processing und C# für die GUI.
-
dot schrieb:
Ich empfehle dir C++ für das Audio Processing und C# für die GUI.
Ja, wäre wohl die Beste Lösung, dann käme ich sicherlich besser voran
-
GoaZwerg schrieb:
(womit kriege ich das kompiliert? MinGW meckert das er ctime nicht kennt):
Hm..? Komischer Fehler. Probier mal mit:
g++ -std=c++0x -O3 -o freq.exe freq.cpp
Ich kompiliere sonst mit Visual Studio. Das Programm braucht bei mir mit VS ~9 und mit GCC ~10 Sekunden. Die teuerstens Zeilen sind:
dcOffset = -0.49 / max; // 16.6% // .. out = leaky * out + dcOffset / posSinc; // 76.3%
Wahrscheinlich kann man da aber nicht mehr so viel optimieren.. bist du denn jetzt mit dem Ergebnis zufrieden? (Insofern du das denn kompilieren kannst, MinGW mit GCC 4.6.1 läuft aber problemlos durch.)
Edit:
dot schrieb:
Ich empfehle dir C++ für das Audio Processing und C# für die GUI.
Und ich empfehle dir für beides C#, da du das 1. besser zu können scheinst, du 2. kein Gefummel mit zwei Sprachen hast und C# 3. mit Sicherheit schnell genug ist.
(Nur wenn das Audiozeug plattformunabhängig sein soll, dann nimm vielleicht doch lieber C++.)
-
Die Performance ist der Grund weshalb ich ihm für den Teil zu C++ raten würde. C# ist sicher ausreichend für einfache Dinge und man kann mit unsafe Blöcken und so schon einiges rausholen. Aber wenn er wirklich möglichst effizient komplexeres Streamprocessing betreiben will, dann wird er SSE verwenden wollen, und da geht's eben in Bereiche wo man mit C# keine Chance hat.
Wenns also was einfaches sein soll, reicht C# vermutlich aus. Aber bei komplexeren Operationen wird man früher oder später C++ aufsuchen, wenn man Performance will.
Wenn es also was Komplexeres ist und performant sein soll, dann würd ich für den performanceintensiven Teil gleich auf C++ setzen, anstatt früher oder später den C# Code nach C++ zu portieren. Eine einfache dll + C-API könnte ausreichen und das ist in C# sehr einfach einzubinden.
Ansonsten natürlich einfach alles in C# machen.
-
Ja, optimiert werden müsste dieser Code eh nicht, dass war nur zum testen, in echt würden die Sinc-Funktionen ja vorberechnet werden.
Mit Visual Studio hat das kompilieren geklappt, danke
Die C# Version ist bei mir etwas mehr als 1 Sekunde langsamer, bei unterschiedlichen Funktionen könnte sich das sicherlich auch wieder relativieren.
Aber ich werd das jetzt so machen das ich die GUI in C# und die kritischen Sachen in einer C++ DLL auslagere.
Danke euch für alle Tipps