3ds-Loader (Performance)
-
Hallo,
ich habe einen 3ds-Loader geschrieben. Nun würde ich gerne wissen, ob er "schnell" genug ist. Der Loader liest alle Objekte, Mappingkoordinaten, Vertices, Objektnamen und Faces. Bei einem Model mit 50643 Punkten und 74508 Faces braucht er im Durchschnitt bei einem Debugbuild um die 7.20ms. Ist das schnell oder eher langsam? Das Model ist zwar schon groß, aber immerhin ist das auch fast 1 Sekunde.Ich hoffe auf hilfreiche Antworten, vielleicht ist ja auch jemand so gut und misst die vergangene Zeit bei seinem Loader.
Vielen Dank im Voraus!
-
Kóyaánasqatsi schrieb:
Bei einem Model mit 50643 Punkten und 74508 Faces braucht er im Durchschnitt bei einem Debugbuild um die 7.20ms. [..] Das Model ist zwar schon groß, aber immerhin ist das auch fast 1 Sekunde.
Warum sind 7 Millisekunden fast 1 Sekunde?
-
Wie hast du denn das gemessen? 7 komma 2 msec?!
Oder ist das ein Mittelwert? (edit) ok, steht ja da
Und was hast du für Hardware? Ich würd erstmal sagen, das das ganz gut ist. Wenn du mir das Model irgendwo hochlädst kann ich das mal mit meinem 3ds Loader vergleichen, wenn du willst.
-
1. 7ms ist ziemlich weit weg von 1s
2. Messungen im Debug Mode sind nahezu ohne Aussage
-
@TyRoXx, this->that
Verzeiht mir die Aussage, wie ihr seht ist der Thread morgens um 4 Uhr entstanden@Cpp_Junky
Hey, das wäre nett wenn du es mal durch deinen Loader jagen würdest. Hier der Link:
http://download399.mediafire.com/3vyfwklbfctg/jjxmzyuqiym/3D-trees-plants-set2.rar ("tree_in_vase2.3ds")
Ich habe einen "normalen" Intel Core 2 Duo mit á 2,4GHz. 4GB RAM und 64-Bit Vista. Danke im Voraus deswegen!Wenn ich es im Releasebuild messe, dauert es im Schnitt 2.30ms.
-
Kóyaánasqatsi schrieb:
...
@Cpp_Junky
Hey, das wäre nett wenn du es mal durch deinen Loader jagen würdest. Hier der Link:
http://download399.mediafire.com/3vyfwklbfctg/jjxmzyuqiym/3D-trees-plants-set2.rar ("tree_in_vase2.3ds")
Ich habe einen "normalen" Intel Core 2 Duo mit á 2,4GHz. 4GB RAM und 64-Bit Vista. Danke im Voraus deswegen!Wenn ich es im Releasebuild messe, dauert es im Schnitt 2.30ms.
Also meiner macht im Schnitt als Release ~60 msec bei dem Model. Ich lade Vertices, Normals, Faces und Materialdaten (Ohne Texturen natürlich).
Hm, dein Model ist ~1,7 MB gross, das bedeutet deine Platte hat einen effektiven Durchsatz von (1000 / 7) x 1,7 MB >= 240 MB/Sek ? Ist das nicht etwas viel? Die Differenz könnte natürlich auch an meiner schlimmst fragmentierten Mühle hier liegen (Ist ein Intel E5800 3,2ghz, 2GB Ram, Windows XP)
-
Cpp_Junky schrieb:
Hm, dein Model ist ~1,7 MB gross, das bedeutet deine Platte hat einen effektiven Durchsatz von (1000 / 7) x 1,7 MB >= 240 MB/Sek ? Ist das nicht etwas viel?
Er hat gar keine Festplatte erwähnt, also ist das Laden der Datei in der gemessenen Zeit wohl nicht enthalten.
EDIT @Kóyaánasqatsi: Warum zeigst du nicht einfach deinen Code?
-
TyRoXx schrieb:
...Er hat gar keine Festplatte erwähnt, also ist das Laden der Datei in der gemessenen Zeit wohl nicht enthalten....
Kann mir nicht vorstellen, wie man einen 3DS Loader testen will, ohne die Datei dabei auszulesen
Viel zu berechnen gibts da nämlich nicht
-
TyRoXx schrieb:
Cpp_Junky schrieb:
Hm, dein Model ist ~1,7 MB gross, das bedeutet deine Platte hat einen effektiven Durchsatz von (1000 / 7) x 1,7 MB >= 240 MB/Sek ? Ist das nicht etwas viel?
Er hat gar keine Festplatte erwähnt, also ist das Laden der Datei in der gemessenen Zeit wohl nicht enthalten.
EDIT @Kóyaánasqatsi: Warum zeigst du nicht einfach deinen Code?
Darauf wollte ich eigentlich verzichten. Naja egal, hier der Code:
#include <iostream> #include <fstream> #include <string> #include <sstream> #include <cassert> #include <vector> #include <conio.h> //>----- Entry point (Primary Chunk at the start of the file ---------------- #define PRIMARY 0x4D4D //>----- Main Chunks -------------------------------------------------------- #define EDIT3DS 0x3D3D // Start of our actual objects #define KEYF3DS 0xB000 // Start of the keyframe information //>----- General Chunks ----------------------------------------------------- #define VERSION 0x0002 #define MESH_VERSION 0x3D3E #define KFVERSION 0x0005 #define COLOR_F 0x0010 #define COLOR_24 0x0011 #define LIN_COLOR_24 0x0012 #define LIN_COLOR_F 0x0013 #define INT_PERCENTAGE 0x0030 #define FLOAT_PERC 0x0031 #define MASTER_SCALE 0x0100 #define IMAGE_FILE 0x1100 #define AMBIENT_LIGHT 0X2100 //>----- Object Chunks ----------------------------------------------------- #define NAMED_OBJECT 0x4000 #define OBJ_MESH 0x4100 #define MESH_VERTICES 0x4110 #define VERTEX_FLAGS 0x4111 #define MESH_FACES 0x4120 #define MESH_MATER 0x4130 #define MESH_TEX_VERT 0x4140 #define MESH_XFMATRIX 0x4160 #define MESH_COLOR_IND 0x4165 #define MESH_TEX_INFO 0x4170 #define HEIRARCHY 0x4F00 //>----- Material Chunks --------------------------------------------------- #define MATERIAL 0xAFFF #define MAT_NAME 0xA000 #define MAT_AMBIENT 0xA010 #define MAT_DIFFUSE 0xA020 #define MAT_SPECULAR 0xA030 #define MAT_SHININESS 0xA040 #define MAT_FALLOFF 0xA052 #define MAT_EMISSIVE 0xA080 #define MAT_SHADER 0xA100 #define MAT_TEXMAP 0xA200 #define MAT_TEXFLNM 0xA300 #define OBJ_LIGHT 0x4600 #define OBJ_CAMERA 0x4700 //>----- KeyFrames Chunks -------------------------------------------------- #define ANIM_HEADER 0xB00A #define ANIM_OBJ 0xB002 #define ANIM_NAME 0xB010 #define ANIM_POS 0xB020 #define ANIM_ROT 0xB021 #define ANIM_SCALE 0xB022 namespace Modelloader { class Vector3f { public: float m_x; float m_y; float m_z; Vector3f( ) { } Vector3f( const float x, const float y, const float z ); }; class Vector3i { public: int m_x; int m_y; int m_z; Vector3i( ) { } Vector3i( const int x, const int y, const int z ); }; class Vector3i16 { public: short m_x; short m_y; short m_z; Vector3i16( ) { } Vector3i16( const int x, const int y, const int z ); }; class Vector2f { public: float m_x; float m_y; Vector2f( ) { } Vector2f( const float x, const float y ); }; class C3dsInStream { protected: std::ifstream m_Stream; std::stringstream m_StringStream; bool m_AbleToOpen; C3dsInStream( const char *Filename ); ~C3dsInStream( ); void CharToInt32( const char *Buffer, int &Int32 ) const; void CharToUInt32( const char *Buffer, unsigned int &UInt32 ) const; void CharToInt16( const char *Buffer, short &Int16 ) const; void CharToUInt16( const char *Buffer, unsigned short &UInt16 ) const; void CharToFloat( const char *Buffer, float &Float ); void CharToLong( const char *Buffer, long &Long ); void CharToDouble( const char *Buffer, double &Double ); void CharToString( const char *Buffer, std::string &String ); }; struct Faces : public Vector3i16 { unsigned short m_Flags; }; struct ObjectBlock { std::string m_ObjectName; std::vector< Vector3f > m_Vertices; std::vector< Faces > m_Faces; std::vector< Vector2f > m_MappingCoords; }; struct Chunk { short m_ID; unsigned int m_Length; }; class C3dsLoader : public C3dsInStream { public: C3dsLoader( const char *Filename ); ~C3dsLoader( ); private: Chunk m_ActChunk; char m_Buffer2[ 2 ]; char m_Buffer4[ 4 ]; std::vector< ObjectBlock > m_Objects; void ReadChunkHeader( ); void SkipChunk( const unsigned int Length ); void ReadChunkID( ); void ChunkLoop( ); // Notice the hierarchy below void ReadObjectChunk( unsigned int Length ); void ReadMeshData( unsigned int Length ); void ReadVertices( ObjectBlock &ActObj, unsigned int Length ); void ReadFaces( ObjectBlock &ActObj ); void ReadMappingCoords( ObjectBlock &ActObj ); }; } #include "3dsLoader.h" namespace Modelloader { Vector3f::Vector3f( const float x, const float y, const float z ) : m_x( x ), m_y( y ), m_z( z ) { } Vector3i::Vector3i( const int x, const int y, const int z ) : m_x( x ), m_y( y ), m_z( z ) { } Vector2f::Vector2f( const float x, const float y ) : m_x( x ), m_y( y ) { } C3dsInStream::C3dsInStream( const char *Filename ) : m_Stream( Filename, std::ifstream::binary ) { if( !m_Stream.is_open( ) && !m_Stream.good( ) ) { m_AbleToOpen = false; // assert will be called // Do something } else { m_AbleToOpen = true; } } C3dsInStream::~C3dsInStream( ) { m_Stream.close( ); } void C3dsInStream::CharToInt32( const char *Buffer, int &Int32 ) const { Int32 = static_cast< int > ( ( static_cast< char > ( Buffer[ 3 ] ) << 24 ) | ( static_cast< char > ( Buffer[ 2 ] ) << 16 ) | ( static_cast< char > ( Buffer[ 1 ] ) << 8 ) | ( static_cast< char > ( Buffer[ 0 ] ) ) ); } void C3dsInStream::CharToUInt32( const char *Buffer, unsigned int &UInt32 ) const { UInt32 = static_cast< unsigned int > ( ( static_cast< unsigned char > ( Buffer[ 3 ] ) << 24 ) | ( static_cast< unsigned char > ( Buffer[ 2 ] ) << 16 ) | ( static_cast< unsigned char > ( Buffer[ 1 ] ) << 8 ) | ( static_cast< unsigned char > ( Buffer[ 0 ] ) ) ); } void C3dsInStream::CharToInt16( const char *Buffer, short &Int16 ) const { Int16 = static_cast< short > ( ( static_cast< unsigned char > ( Buffer[ 1 ] ) << 8 | static_cast< unsigned char > ( Buffer[ 0 ] ) ) ); } void C3dsInStream::CharToUInt16( const char *Buffer, unsigned short &UInt16 ) const { UInt16 = static_cast< unsigned short > ( ( static_cast< unsigned char > ( Buffer[ 1 ] ) << 8 ) | ( static_cast< unsigned char > ( Buffer[ 0 ] ) ) ); } void C3dsInStream::CharToFloat( const char *Buffer, float &Float ) { ( ( char * ) &Float )[ 0 ] = Buffer[ 0 ]; ( ( char * ) &Float )[ 1 ] = Buffer[ 1 ]; ( ( char * ) &Float )[ 2 ] = Buffer[ 2 ]; ( ( char * ) &Float )[ 3 ] = Buffer[ 3 ]; } void C3dsInStream::CharToLong( const char *Buffer, long &Long ) { Long = ::atol( Buffer ); } void CharToDouble( const char *Buffer, double &Double ) { Double = ::atof( Buffer ); } void C3dsInStream::CharToString( const char *Buffer, std::string &String ) { for( unsigned int i( 0); i = ::strlen( Buffer ); i++) { String.push_back( Buffer[ i ] ); } } C3dsLoader::C3dsLoader( const char *Filename ) : C3dsInStream( Filename ) { if( !C3dsInStream::m_Stream.good( ) || C3dsInStream::m_Stream.fail( ) || C3dsInStream::m_Stream.bad( ) ) { throw std::runtime_error("Failed to operate with 3ds-File!"); } ReadChunkHeader( ); if( static_cast< short > ( m_ActChunk.m_ID ) != PRIMARY ) { std::cout<<"3ds-File is damaged!"<<std::endl; } C3dsInStream::m_Stream.seekg( 10, C3dsInStream::m_Stream.ios_base::cur ); // Skip the 10 useless byte ChunkLoop( ); // Begin to read neccessary chunks } C3dsLoader::~C3dsLoader( ) { } void C3dsLoader::ReadChunkHeader( ) { C3dsInStream::m_Stream.read( m_Buffer2, sizeof( m_ActChunk.m_ID ) ); C3dsInStream::CharToInt16( m_Buffer2, m_ActChunk.m_ID ); C3dsInStream::m_Stream.read( m_Buffer4, sizeof( m_ActChunk.m_Length ) ); C3dsInStream::CharToUInt32( m_Buffer4, m_ActChunk.m_Length ); } void C3dsLoader::ReadChunkID( ) { C3dsInStream::m_Stream.read( m_Buffer2, sizeof( m_ActChunk.m_ID ) ); C3dsInStream::CharToInt16( m_Buffer2, m_ActChunk.m_ID ); } void C3dsLoader::SkipChunk( const unsigned int Length ) { C3dsInStream::m_Stream.seekg ( m_ActChunk.m_Length - ( sizeof( m_ActChunk.m_ID ) + sizeof( m_ActChunk.m_Length ) ), // Subtract the ID + Length C3dsInStream::m_Stream.ios_base::cur ); } void C3dsLoader::ChunkLoop( ) { while( !C3dsInStream::m_Stream.eof( ) ) { ReadChunkHeader( ); switch( m_ActChunk.m_ID ) { case static_cast< short > ( EDIT3DS ): ReadObjectChunk( m_ActChunk.m_Length ); break; default: SkipChunk( m_ActChunk.m_Length ); break; } } m_Objects; } void C3dsLoader::ReadObjectChunk( unsigned int Length ) { Length -= ( sizeof( m_ActChunk.m_ID ) + sizeof( m_ActChunk.m_Length ) ); const unsigned int ActPos = C3dsInStream::m_Stream.tellg( ); while( static_cast< unsigned int > ( C3dsInStream::m_Stream.tellg( ) ) != ( ActPos + Length ) ) { ReadChunkHeader( ); switch( m_ActChunk.m_ID ) { case static_cast< short > ( NAMED_OBJECT ): ReadMeshData( m_ActChunk.m_Length ); break; default: SkipChunk( m_ActChunk.m_Length ); break; } } } void C3dsLoader::ReadMeshData( unsigned int Length ) { ObjectBlock NewObject; Length -= ( sizeof( m_ActChunk.m_ID ) + sizeof( m_ActChunk.m_Length ) ); const unsigned int ActPos = C3dsInStream::m_Stream.tellg( ); // Read the object name m_Buffer2[ 0 ] = -1; while( m_Buffer2[ 0 ] != '\0' ) { m_Buffer2[ 0 ] = C3dsInStream::m_Stream.get( ); NewObject.m_ObjectName.push_back( m_Buffer2[ 0 ] ); } while( static_cast< unsigned int > ( C3dsInStream::m_Stream.tellg( ) ) != ( ActPos + Length ) ) { ReadChunkHeader( ); switch( m_ActChunk.m_ID ) { case static_cast< short > ( OBJ_MESH ): ReadVertices( NewObject, m_ActChunk.m_Length ); break; default: SkipChunk( m_ActChunk.m_Length ); } } // When everything is read below this parent-chunk, save the object m_Objects.push_back( NewObject ); } void C3dsLoader::ReadVertices( ObjectBlock &ActObj, unsigned int Length ) { Length -= ( sizeof( m_ActChunk.m_ID ) + sizeof( m_ActChunk.m_Length ) ); const unsigned int ActPos = C3dsInStream::m_Stream.tellg( ); ReadChunkHeader( ); unsigned short NumVertices; Vector3f Vertices; C3dsInStream::m_Stream.read( m_Buffer2, sizeof( NumVertices ) ); C3dsInStream::CharToUInt16( m_Buffer2, NumVertices ); for( unsigned short i( 0 ); i != NumVertices; ++i ) { C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float ) ); C3dsInStream::CharToFloat( m_Buffer4, Vertices.m_x ); C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float ) ); C3dsInStream::CharToFloat( m_Buffer4, Vertices.m_y ); C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float ) ); C3dsInStream::CharToFloat( m_Buffer4, Vertices.m_z ); ActObj.m_Vertices.push_back( Vertices ); } while( static_cast< unsigned int > ( C3dsInStream::m_Stream.tellg( ) ) != ( ActPos + Length ) ) { ReadChunkHeader( ); switch( m_ActChunk.m_ID ) { case static_cast< short > ( MESH_FACES ): ReadFaces( ActObj ); break; case static_cast< short > ( MESH_TEX_VERT ): ReadMappingCoords( ActObj ); break; default: SkipChunk( m_ActChunk.m_Length ); } } } void C3dsLoader::ReadFaces( ObjectBlock &ActObj ) { unsigned short NumFaces; Faces Face; C3dsInStream::m_Stream.read( m_Buffer2, sizeof( NumFaces ) ); C3dsInStream::CharToUInt16( m_Buffer2, NumFaces ); for( unsigned short i( 0 ); i != NumFaces; ++i ) { C3dsInStream::m_Stream.read( m_Buffer2, sizeof( short ) ); C3dsInStream::CharToInt16( m_Buffer2, Face.m_x ); C3dsInStream::m_Stream.read( m_Buffer2, sizeof( short ) ); C3dsInStream::CharToInt16( m_Buffer2, Face.m_y ); C3dsInStream::m_Stream.read( m_Buffer2, sizeof( short ) ); C3dsInStream::CharToInt16( m_Buffer2, Face.m_z ); C3dsInStream::m_Stream.read( m_Buffer2, sizeof( short ) ); C3dsInStream::CharToUInt16( m_Buffer2, Face.m_Flags ); ActObj.m_Faces.push_back( Face ); } } void C3dsLoader::ReadMappingCoords( ObjectBlock &ActObj ) { unsigned short NumMappingCoords; Vector2f MappingCoords; C3dsInStream::m_Stream.read( m_Buffer2, sizeof( NumMappingCoords ) ); C3dsInStream::CharToUInt16( m_Buffer2, NumMappingCoords ); NumMappingCoords; for( unsigned short i( 0 ); i != NumMappingCoords; ++i ) { C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float) ); C3dsInStream::CharToFloat( m_Buffer4, MappingCoords.m_x ); C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float ) ); C3dsInStream::CharToFloat( m_Buffer4, MappingCoords.m_y ); ActObj.m_MappingCoords.push_back( MappingCoords ); } } }
-
void C3dsInStream::CharToString( const char *Buffer, std::string &String ) { for( unsigned int i( 0); i = ::strlen( Buffer ); i++) { String.push_back( Buffer[ i ] ); } }
Das wird anscheinend nicht verwendet, denn der Fehler würde bei einem nicht-leeren String auffallen.
Außerdem hat die Schleife eine quadratische Laufzeit (nach der Korrektur des Vergleichsoperators), was total unnötig ist.C3dsInStream::m_Stream.read( m_Buffer4, sizeof( float) ); C3dsInStream::CharToFloat( m_Buffer4, MappingCoords.m_x ); //warum nicht einfach das: C3dsInStream::m_Stream.read(reinterpret_cast<char *>(&MappingCoords.m_x), sizeof(MappingCoords.m_x));
while( static_cast< unsigned int > ( C3dsInStream::m_Stream.tellg( ) ) != ( ActPos + Length ) )
Sieht gefährlich aus. Bist du sicher, dass das bei einer beschädigten Datei keine Endlosschleife ergeben kann?
Zum Thema Geschwindigkeit: Ich weiß nicht wie gut
ifstream::read
puffert, eine eigene Implementation könnte schneller sein.Falls C++11 zur Verfügung steht, würden sich Move-Operationen bei
ObjectBlock
lohnen.ActObj.m_MappingCoords.push_back( MappingCoords );
Die Anzahl der "mapping coords" ist doch bekannt, also nimm
resize
oderreserve
stattpush_back
(kommt mehrmals vor).for( unsigned short i( 0 ); i != NumFaces; ++i )
Bringt nicht viel, sollte aber erwähnt werden:
int
kann schneller sein alsshort
.
-
Also ich halte 7.2ms fuer ausreichend, da ein Laden des Modells im vergleich eher selten vorkommt. Darueber hinaus fallen natuerlich einige Inkonsistenten auf.
CharToFloat
unterscheidet sich stark vonCharToDouble
. Beim ersteren wird das Bitmuster direkt als Float interpretiert, beim letzteren wird von einem null-terminiertem String ausgegangen und konvertiert. Das passt nicht! Darueber hinaus wuerde ich fuer ersteresunion
verwenden und fuer letzteresstingstream
.Was ist, wenn die Datei kleiner als der "Header" ist, also
ReadChunkHeader
nicht angewandt werden darf? Darueber hinaus gibt es Funktionen wieReadChunkID
, die nie verwendet werden.
-
Hallo,
in dem Sinne "Danke" an die Verbesserungsvorschläge, aber es geht nicht um Fehler im Code. Wie ich bereits sagte, wollte ich den Code nicht posten, da er lange noch nicht fertig ist. Es geht rein um die Schnelligkeit. Und ob man nun 2 übersichtliche Funktionen oder eine zusammengehackte Funktion aufruftAlso bitte.
-
Du weisst, dass du verarscht wirst, wenn man dich fragt, ob ein debug Build schnell genug ist.
-
hmmmmmmmm schrieb:
Du weisst, dass du verarscht wirst, wenn man dich fragt, ob ein debug Build schnell genug ist.
Wo wurde ich das denn gefragt?
-
7.2 ms klingt schon sehr schnell. Ich frag mich allerdings warum du 3ds verwendest, wenn du es offenbar auf maximale Ladegeschwindigkeit abgesehen hast. Ich würd da ein eigenes Format entwickeln, das mehr oder weniger einfach ein 1:1 Memory-Dump ist und die Datei dann evtl. gleich direkt in meinen Prozess mappen.
-
Kóyaánasqatsi schrieb:
hmmmmmmmm schrieb:
Du weisst, dass du verarscht wirst, wenn man dich fragt, ob ein debug Build schnell genug ist.
Wo wurde ich das denn gefragt?
Du hast das im ersten Beitrag gefragt:
Nun würde ich gerne wissen, ob er "schnell" genug ist. ... bei einem Debugbuild um die 7.20ms.
-
Als Anwender ärgere ich mich eher, wenn ein Programm beim Modelle Laden abstürzt, als wenn es ein bisschen braucht.
Abgesehen davon solltest du versuchen, die Festplatte anzubetteln, dass sie dir die Daten rechtzeitig rausrückt, anstatt Performancezuwachs im Millisekundenbereich aus deinem Code herausprügeln zu wollen.
Endlosschleifen bei nichtleeren Strings oder semantisch inkonsistente Casts hingegen sind Sachen, die man vermeiden sollte,protected
-Attribute sind nutzlos.
Und was genau unterscheidet deine (fehlerhafte) CharToString-Methode eigentlich vonstd::string::append(const char*)?
Istlexical_cast
(an den - bei dir dank der seltsamen Semantik schwer auszumachenden - entsprechenden Stellen) zu langsam, oder willst du Abhängigkeiten vermeiden? Trifft letzteres zu: Sind stringstreams zu langsam? Wieso übergibst du konstante Werte? Und, wenn man drauf steht, kann man doch solche Vektorfummeleien gar wunderbar in Templates auslagern.
Und - ohne es genau sagen zu können - mich plagt das Gefühl, dass der Codeaufbau überdacht werden sollte, wenn man schon eine Aufrufstruktur durch Einrücken beschreiben muss, damit der Durchblick gewahrt werden kann.Nicht?
-
PS: Und da ist es auch ziemlich Schnurz, ob der Code noch lange nicht fertig ist. Wenn er noch nicht fertig ist, dann braucht dich Performance noch viel weniger zu interessieren als sowieso.