Skip to content

Conversation

VReaperV
Copy link
Contributor

Removes most of the reallocations that happen because the file size is not known in advance.

This decreases the load time at start-up by easily 2x, and slightly improves load time for maps.

@VReaperV
Copy link
Contributor Author

Added the same fix for opus files too.

@illwieckz
Copy link
Member

Very nice!

@VReaperV
Copy link
Contributor Author

I'm not sure what the hell happened with CI there, it never actually ran anything, and the only change I made was rebasing on master.

Copy link
Member

@slipher slipher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This decreases the load time at start-up by easily 2x

Are you sure you're not accidentally measuring the difference between the 2 randomly selected menu soundtracks? One takes around 0.2s to load, and the other around 0.9s.

In any case, it did make a nice ~10% improvement for LoadOggCodec in my tests.

Master, short soundtrack (heartbeat1.ogg)
SoundLoader-ext N=10 min=215355us max=261480us avg=243623us tot=2436236us

This branch, short soundtrack
SoundLoader-ext N=10 min=172695us max=232609us avg=217946us tot=2179463us

Master, long soundtrack (heartbeat.ogg)
SoundLoader-ext N=10 min=820111us max=1047663us avg=921785us tot=9217856us

This branch, long soundtrack
SoundLoader-ext N=10 min=779843us max=934458us avg=837928us tot=8379280us

@@ -383,8 +383,10 @@ namespace Audio {

streams[streamNum]->SetGain(volume);

AudioData audioData(rate, width, channels, (width * numSamples * channels),
reinterpret_cast<const char*>(data));
AudioData audioData { rate, width, channels, width * numSamples * channels };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this leaks data. Though the existing code is an unused and broken mess that frees data using the wrong type

: sampleRate{sampleRate}
, byteDepth{byteDepth}
, numberOfChannels{numberOfChannels}
, size{size}
, rawSamples{rawSamples}
, size( newSize )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need size now that the vector knows its own size?

char* data = new char[size];
AudioData out { sampleRate, byteDepth, numChannels, size };
out.rawSamples.reserve( size );
std::copy_n(audioFile.data() + dataOffset + 8, size, out.rawSamples.data());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rawSamples still has size 0 at this point so you can't write to it. You can use assign here

opus_int16* buffer = new opus_int16[numberOfChannels * 5760];
/* The largest opus file we have so far is sound/thunder/weather.opus
It uncompress to ~7mb. Use 64mb here just in case */
static constexpr uint32_t MAX_USED_FILE_SIZE = 64 * 1024 * 1024;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

64M of int16 is 128MB

static constexpr uint32_t MAX_USED_FILE_SIZE = 64 * 1024 * 1024;
static constexpr uint32_t MAX_READ_SIZE = 1 * 1024 * 1024;

static opus_int16 buffer[MAX_USED_FILE_SIZE];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the extra 64M of memory be allocated permanently seems suboptimal. Why not reserve 64M in the vector and call shrink_to_fit at the end? The code would also be simpler that way.

Copy link
Contributor Author

@VReaperV VReaperV Aug 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that ever work? ov_read() and op_read() expect a pointer, you can't use reserve() there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reserve MAX_USED_FILE_SIZE once at the beginning, then resize adding an additional MAX_READ_SIZE before each read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is straight up worse than the static buffer since it generates an extra ~300mb because each sound's samples have to be >= MAX_READ_SIZE. Reducing MAX_READ_SIZE would defeat the point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, with reserve() + resize(), the vector's size() could no longer be used in place of AudioData::size since the sizes would no longer match.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that ever work? ov_read() and op_read() expect a pointer, you can't use reserve() there.

Yeah you'd have to resize it up by the read size first.

That is straight up worse than the static buffer since it generates an extra ~300mb because each sound's samples have to be >= MAX_READ_SIZE. Reducing MAX_READ_SIZE would defeat the point.

Hence shrink_to_fit.

But anyway, I thought of another solution that allows using the current version of the code mostly unchanged: use Hunk_AllocateTempMemory. This will allow the memory used by the large music file to be recycled, assuming that enough large allocations are made afterward (which should be the case when loading a map).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hence shrink_to_fit.
Only if MAX_READ_SIZE is reduced.

But anyway, I thought of another solution that allows using the current version of the code mostly unchanged: use Hunk_AllocateTempMemory. This will allow the memory used by the large music file to be recycled, assuming that enough large allocations are made afterward (which should be the case when loading a map).
True, that'd probably be the best solution.

@@ -151,20 +151,43 @@ AudioData LoadOggCodec(std::string filename)
int sampleRate = oggInfo->rate;
int numberOfChannels = oggInfo->channels;

char buffer[4096];
/* The largest ogg file we have so far is res-soundtrack/sound/ui/heartbeat.ogg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2 comments I made on OpusCodec apply here as well. Also when I logged it, heartbeat.ogg was 49M samples / 98MB.

Removes most of the reallocations that happen because the file size is not known in advance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants