Zeugma440 / atldotnet

Fully managed, portable and easy-to-use C# library to read and edit audio data and metadata (tags) from various audio formats, playlists and CUE sheets
MIT License
459 stars 61 forks source link

Load Singular Metadata fields? #186

Closed Tangenten closed 1 year ago

Tangenten commented 1 year ago

I'm running through a pretty huge amount of audio files but not collecting that much metadata from each in order to build a database from that metadata, I'm wondering if there's a way to read (load) only select metadata fields in order to reduce memory allocations. In ATL.Settings i have enabled "NullAbsentValues" and disabled "ReadAllMetaFrames", I've also tweaked "FileBufferSize" to 256 which seems to help a bit. Any advice would be appreciated (:

Zeugma440 commented 1 year ago

First of all, I'm quite surprised that memory allocations are an issue, especially at read time.

Also, I can't reply to your question in a generic way, as every audio format has its own properties. Do you scan miscellaneous formats, or is there one in particular (e.g. WAV, FLAC, M4A...) that we should focus on?

Tangenten commented 1 year ago

It's not a huge issue really, but it does stress the garbage collector a bit. which is somewhat expected when reading such a huge number of files (500 000+). Right now, I'm calling new Track(filepath) continuously from as many processor cores as I can spin up, reading the fields "Title", "Comment" and "Description". I shouldn't be holding a reference to the Track or any of its fields as I'm working using Span(char) from the metadata fields and writing those as bytes to the database, and the garbage collector does free the memory so it's not like I'm running out of memory here or spooling to disk. As far as I know there shouldn't any embedded pictures almost at all as the files I'm reading are sound effects which as far I know don't usually dont come with pictures, is it possible to skip reading the picture data if there are? Cant see anything surrounding picture data in the memory profiler.

I'm guessing I have more .wav files than .ogg files, but I'm seeing that .ogg files are by far the biggest in terms of allocations. In the "Ogg.getInfo(BufferedBinaryReader, FileInfo, ReadTagParams)" method it seems you're writing something back to the file which is coming in at 276 GB allocations in byte[] in the large object heap, seems to be an additional 110gb in that "Ogg.getInfo" method Aswell, byte[] again but in the small object heap this time, not sure if that ones related to memory writes.

at MemoryStream.set_Capacity(int) at MemoryStream.EnsureCapacity(int) at MemoryStream.Write(byte[], int, int) at Ogg.getInfo(BufferedBinaryReader, FileInfo, ReadTagParams) at Ogg.Read(Stream, ReadTagParams)

This is probably related to the spikes in file I/O and memory allocations I'm seeing.

The other considerable memory allocations I'm seeing are in the "BextTag.FromStream(Stream, MetaDataIO, ReadTagParams)", specifically "Latin1Encoding.GetString(byte[], int, int)" method with .wav files but they pale in comparison (~40GB). It's possible to avoid these types of allocations by working with Span(char) and Span(byte) as much as possible before creating a final string but that would require a major rewrite I'm guessing.

Wonder if implementing a IDisposable interface for Track would avoid the memory spikes, But might slow down overall performance though...

Anyway, this isn't critical for the functionality of the app I'm building but it would be nice to possibly avoid spooling to disk (maybe the GC kicks in order to avoid this though) or stressing the users file I/O, I thought I might be able to build the database faster by reducing the amount of data I'm reading (loading only the fields I'm actually using) but looking at the profiler now it looks like 95% of the time is spent in the FileStream constructor anyway so I probably wouldn't gain any performance even if there was a way to read singular fields :D but the .ogg method should probably be looked into if possible

Zeugma440 commented 1 year ago

Thanks a lot for the detailed feedback!

the files I'm reading are sound effects which as far I know don't usually dont come with pictures, is it possible to skip reading the picture data if there are?

If there's no embedded picture data, nothing will happen. Don't worry about that. I was just trying to guess what could cause significant memory allocation.


What you're observing when reading OGG files is due to the unique, streaming-oriented way these files are structured (bitstreams comprised of packets, broken down into multiple pages, with the possibility of multiplexing on top of it).

Because of that, when reading OGG files, ATL recreates well-ordered, demultiplexed data inside temporary MemoryStreams before parsing the file. I fear the gap to cross to lower memory footprint (i.e. directly parsing the packets) will cause code complexity to skyrocket.


The other considerable memory allocations I'm seeing are in the "BextTag.FromStream(Stream, MetaDataIO, ReadTagParams)", specifically "Latin1Encoding.GetString(byte[], int, int)" method with .wav files but they pale in comparison (~40GB). It's possible to avoid these types of allocations by working with Span(char) and Span(byte) as much as possible before creating a final string but that would require a major rewrite I'm guessing.

Now there's definitely something smart to do there. I'll go in that direction and let you know :)

Zeugma440 commented 1 year ago

Following up regarding BEXT tag handling inside WAV : all my attempts at tweaking the code ended up with equivalent performance as v4.24 current implementation in terms of speed and allocation.

Using Span ❤️ from end to end might be the key to that issue, but it would require to raise minimum .NET Framework version to 5 to be able to use C# 8.0, which I'm not sure all users will follow. Some of them even forked and downgraded the library back to .NET 4.5 when I switched to .NET 4.8 two years ago🤦

Zeugma440 commented 1 year ago

I've done the best I could with what I have right now :

Zeugma440 commented 1 year ago

OGG tweak is available in today's v4.25