Skip to content

[GEN][ZH] Reduce cost of GameMessage::getCommandTypeAsAsciiString by 70% #1259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

Caball009
Copy link

@Caball009 Caball009 commented Jul 10, 2025

AsciiString GameMessage::getCommandTypeAsAsciiString(GameMessage::Type t)
{
#define CHECK_IF(x) if (t == x) { return #x; }
AsciiString commandName = "UnknownMessage";
if (t >= GameMessage::MSG_COUNT)
{
commandName = "Invalid command";
}
CHECK_IF(MSG_INVALID)
CHECK_IF(MSG_FRAME_TICK)
CHECK_IF(MSG_RAW_MOUSE_BEGIN)
CHECK_IF(MSG_RAW_MOUSE_POSITION)
CHECK_IF(MSG_RAW_MOUSE_LEFT_BUTTON_DOWN)
CHECK_IF(MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK)
CHECK_IF(MSG_RAW_MOUSE_LEFT_BUTTON_UP)
CHECK_IF(MSG_RAW_MOUSE_LEFT_CLICK)
CHECK_IF(MSG_RAW_MOUSE_LEFT_DRAG)
CHECK_IF(MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN)
CHECK_IF(MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK)
CHECK_IF(MSG_RAW_MOUSE_MIDDLE_BUTTON_UP)

This PR changes a long list of if statements in getCommandTypeAsAsciiString into a single switch statement for slightly better performance.

TODO

  • Replicate in Generals
  • Measure performance

@Caball009 Caball009 added Minor Severity: Minor < Major < Critical < Blocker Performance Is a performance concern Gen Relates to Generals ZH Relates to Zero Hour labels Jul 10, 2025
@Caball009
Copy link
Author

Caball009 commented Jul 10, 2025

Reviewing will probably be easier per commit. I tried to keep the commits as clean as possible; the first two only change the macro name and nothing else.

@Caball009 Caball009 linked an issue Jul 10, 2025 that may be closed by this pull request
@xezon
Copy link

xezon commented Jul 11, 2025

Can you measure the cost of this function please?

@Caball009
Copy link
Author

Caball009 commented Jul 11, 2025

Can you measure the cost of this function please?

Measure what exactly? In isolation or in-game? It's going to be an improvement either way, but very minimal. This code is only used in debug mode (well, DEBUG_LOGGING) so not even modern compilers will turn the original code to a switch statement; they do it in release mode, though.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Looks correct.

@aliendroid1
Copy link

is the point of the original code to be a stupid hack to check the enum against the enum name?

Copy link

@aliendroid1 aliendroid1 left a comment

Choose a reason for hiding this comment

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

If it does not yeild a measurable performance boost then its not a change worth making now especially if it only affects debug builds and can be done better after dropping vc6.

But there are other reasons to consider updating the code. The original code does looks very hacky, relying on some weird macro tricks.

We should investigate what is really happening here and see if it can be done with more standard syntax.

@aliendroid1
Copy link

Actually the implementation is correct but since we have access to modern text editors and can edit multiple lines at the same time, I think its better to skip the macro altogether and just use switch statements directly. Having to write the enum name twice is really not much work with find replace and regex

@Caball009
Copy link
Author

Actually the implementation is correct but since we have access to modern text editors and can edit multiple lines at the same time, I think its better to skip the macro altogether and just use switch statements directly. Having to write the enum name twice is really not much work with find replace and regex

I'm hardly a fan of macros but think it's fine here for the time being. It's simple and it's local to the function so it doesn't leak through to other code.

Having to write the enum name twice is really not much work with find replace and regex

This would introduce the potential for typos or string literals that don't match (changed) enum values, that's just bad for maintainability imo.

Copy link

@aliendroid1 aliendroid1 left a comment

Choose a reason for hiding this comment

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

its inherently problamatic code so unfortunately there isn't really a good local fix especially with just c++98 so I think your implementation is a decent enough improvement for the time being

@@ -230,421 +230,435 @@ AsciiString GameMessage::getCommandAsAsciiString( void )

AsciiString GameMessage::getCommandTypeAsAsciiString(GameMessage::Type t)
Copy link

Choose a reason for hiding this comment

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

Another oprimization we can do here is return const char* instead of AsciiString, because it is not necessary to allocate here.

Copy link
Author

Choose a reason for hiding this comment

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

Right. I can change that. That does require a change to getContentsAsAsciiString as well. Should I just do the change or remove the entire function? (#1259 (comment))

Copy link

Choose a reason for hiding this comment

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

We can remove the functions if you like.

Copy link
Author

Choose a reason for hiding this comment

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

Changed return type to const char* and removed function AsciiString getContentsAsAsciiString.

@xezon xezon added the Refactor Edits the code with insignificant behavior changes, is never user facing label Jul 13, 2025
@@ -228,456 +228,469 @@ AsciiString GameMessage::getCommandAsAsciiString( void )
return getCommandTypeAsAsciiString(m_type);
}

AsciiString GameMessage::getCommandTypeAsAsciiString(GameMessage::Type t)
const char* GameMessage::getCommandTypeAsAsciiString(GameMessage::Type t)
Copy link

@xezon xezon Jul 14, 2025

Choose a reason for hiding this comment

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

How about we remove this function as well? It is unused. Or did I look wrong?

Other than that, the function name is getCommandTypeAsAsciiString but it now returns const char*. It would have to be named getCommandTypeName then.

Copy link
Author

Choose a reason for hiding this comment

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

That one is still used. I updated the names and return types of both functions, as well as the function calls for getCommandAsAsciiString.

@xezon xezon added Debug Is mostly debug functionality and removed Refactor Edits the code with insignificant behavior changes, is never user facing labels Jul 14, 2025
@xezon
Copy link

xezon commented Jul 14, 2025

Performance measurement

{
	std::srand(111);
	int64_t begin = GetTickCount64();
	GameMessage::Type t = GameMessage::Type(std::rand() % GameMessage::MSG_COUNT);
	//GameMessage::Type t = GameMessage::Type(GameMessage::MSG_COUNT / 2);
	for (Int i = 0; i < 10000000; ++i)
	{
		AsciiString str = GameMessage::getCommandTypeAsAsciiString(t);
		DEBUG_ASSERTCRASH(!str.isEmpty());
	}

	int64_t time = GetTickCount64() - begin;
	DEBUG_LOG(("time old %lld ms", time));
}
{
	std::srand(111);
	int64_t begin = GetTickCount64();
	GameMessage::Type t = GameMessage::Type(std::rand() % GameMessage::MSG_COUNT);
	//GameMessage::Type t = GameMessage::Type(GameMessage::MSG_COUNT / 2);
	for (Int i = 0; i < 10000000; ++i)
	{
		AsciiString str = GameMessage::getCommandTypeAsString(t);
		DEBUG_ASSERTCRASH(!str.isEmpty());
	}

	int64_t time = GetTickCount64() - begin;
	DEBUG_LOG(("time new %lld ms", time));
}

Result

time old 8390 ms
time new 2375 ms

Over 70% faster.

@xezon xezon changed the title [GEN][ZH] Improve efficiency in GameMessage::getCommandTypeAsAsciiString [GEN][ZH] Reduce cost of GameMessage::getCommandTypeAsAsciiString by 70% Jul 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Debug Is mostly debug functionality Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Performance Is a performance concern ZH Relates to Zero Hour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GameMessage::getCommandTypeAsAsciiString looks inefficient
4 participants