Skip to content
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

Improvements on HJson creation #47

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

lawnighthawk
Copy link
Contributor

This change adds functions for insertion into the map and push_back into vector that use r-value references, allowing for functions that build HJson::Value recursively to skip allocations and copies.

Adding functions for Vector and Map that use r-value references
for insertion, useful when builing a hjson value recursively
@trobro
Copy link
Member

trobro commented Dec 17, 2023

std::map::insert_or_assign() was introduced in C++17 but hjson-cpp only requires C++11. I'm reluctant to drop support for C++11.

@lawnighthawk
Copy link
Contributor Author

lawnighthawk commented Dec 17, 2023 via email

@lawnighthawk
Copy link
Contributor Author

@trobro There is a new version now that has #define guards

@trobro
Copy link
Member

trobro commented Dec 18, 2023

Having c++ version guards in the implementation is fine, but having c++ version guards in the header can cause confusing problems for users. This project compiles into a library that users might install on their system. But perhaps they compile hjson-cpp as c++11, and then at some point in the future try to link it from an application built as c++17. The header seen by the c++17 appliction would then not represent the content of the hjson-cpp lib, there would be functions declared in the header that are not present in the lib.

I'm wondering if the performance gains in this PR are worth the added complexity? Have you benchmarked it?

If the performance gains are worth it, perhaps the move can be implemented without insert_or_assign()?

case Type::Map:
try {
prv->m->m.insert_or_assign(key, std::move(other));
} catch(const std::out_of_range&) {}
Copy link
Member

Choose a reason for hiding this comment

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

What would cause std::out_of_range to be thrown, and why should it be ignored?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right in that it does not make a lot of sense, maybe just not have a catch and throw whatever exception came from the container?

Copy link
Member

Choose a reason for hiding this comment

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

Yes better to not have a catch.

} catch(const std::out_of_range&) {}
return;
default:
throw type_mismatch("Must be of type Map for that operation.");
Copy link
Member

Choose a reason for hiding this comment

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

The implementation seems to support type Undefined too, not just type 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.

true, this implementation follows a similar scheme as to that of operator [] , where if the Value is of type undefined, we assume the user wanted map and thus we convert it to a map type and then treat it as a map insertion.

@lawnighthawk
Copy link
Contributor Author

@trobro I have tried to build a benchmark showing the value of doing this, but with the latest master the benchmark showed 10% improvement in some cases, none in others. This might not be enough to justify the headaches, I'll take more time to dig deeper. The original use case for this was building jsons on the fly as a way to have a data structure to share variable field information between functions/processes. Thus the speed of json creation was important, and the jsons were many and large.

@trobro
Copy link
Member

trobro commented Dec 22, 2023

A consistent 10 % improvement of performance could justify adding complexity. But I noticed now one operation missing from your implementation: adding new keys to the vector that keeps track of the insertion order in the map.

Perhaps you can change your implementation into this before you benchmark:

void Value::insert_or_assign(const std::string& key, Value&& other) {
  if (prv->type == Type::Undefined) {
    prv->~ValueImpl();
    // Recreate the private object using the same memory block.
    new(&(*prv)) ValueImpl(Type::Map);
  } else if (prv->type != Type::Map) {
    throw type_mismatch("Must be of type Undefined or Map for that operation.");
  }

  auto it = prv->m->m.find(name);
  if (it == prv->m->m.end()) {
    // If the key is new we must add it to the order vector also.
    prv->m->v.push_back(key);

    prv->m->m.emplace(key, std::move(other));
  } else {
    it.second = std::move(other);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants