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

Performance and safety improvements #429

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

Conversation

mikucionisaau
Copy link
Contributor

@mikucionisaau mikucionisaau commented Oct 23, 2024

  • replace std::regex with std:string_view scanning (thousands of times faster)
  • replace std::tuple with dedicated Version class with named members
  • add virtual destructor to backend_interface for safety
  • replaced value passing with const & in fplot and more
  • clang_tidy suggests making lots of const methods and more...

@mikucionisaau mikucionisaau marked this pull request as ready for review October 24, 2024 07:56
@mikucionisaau
Copy link
Contributor Author

mikucionisaau commented Oct 24, 2024

Rebased and tested with GCC/Debian/testing, Windows Visual Studio Community 2022 and AppleClang/MacOS.
Feels faster and snappier.
The rest of clang-tidy suggestions are too minor and too numerous.

Potential reservations: changes the API subtly, not sure about consequences for other projects which use this.
I think this is ready and worth merging.

Copy link
Owner

@alandefreitas alandefreitas left a comment

Choose a reason for hiding this comment

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

Nice. Removing std::regex is refreshing. :)

}; // class backend_interface

/// Captures (backend) version information.
struct Version {
Copy link
Owner

Choose a reason for hiding this comment

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

This needs MATPLOT_EXPORTS if it's part of the API.

Copy link
Owner

Choose a reason for hiding this comment

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

It would also be good to maintain the snake_case convention. version or maybe gnuplot::version since there's no other backend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll make it snake case, sorry.
I could also move Version to gnuplot, I just thought the class is more general than just gnuplot.

Copy link
Owner

Choose a reason for hiding this comment

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

Yes. Outside gnuplot is also OK. I was thinking about the name-grabbing, but that's also OK. We won't need matplot::version for anything else.

std::string gnuplot::default_terminal_type() {
static std::string terminal_type;
const bool dont_know_term_type = terminal_type.empty();
if (dont_know_term_type) {
terminal_type = run_and_get_output("gnuplot -e \"show terminal\" 2>&1");
terminal_type = std::regex_replace(terminal_type,
std::regex("[^]*terminal type is ([^ ]+)[^]*"), "$1");
terminal_type = word_after(terminal_type, "terminal type is ");
Copy link
Owner

Choose a reason for hiding this comment

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

Heh... Great! :)

if (!version) { // unknown version
const auto version_str = run_and_get_output("gnuplot --version 2>&1");
const auto major = word_after(version_str, "gnuplot");
const auto minor = word_after(major, ".");
Copy link
Owner

Choose a reason for hiding this comment

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

This pattern is a little confusing when the function is called repeatedly. Let's say the output is "... gnuplot 5.2 patchlevel 6". Wouldn't major just be "5"? If so, then there's no word after "." because major doesn't include everything after 5. That is, major is "5" and not "5.2 patchlevel 6". On the other hand, if major does contain "5.2 patchlevel 6", then the function name and return value are a little misleading.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, the name major is misleading, I have renamed it to major_minor and added example in comments -- I think it's clearer when an example is upfront.

@@ -47,7 +47,7 @@ namespace matplot::backend {
/// Identify the default terminal type in the system
static std::string default_terminal_type();
static bool terminal_is_available(std::string_view);
static std::tuple<int, int, int> gnuplot_version();
static Version gnuplot_version();
Copy link
Owner

Choose a reason for hiding this comment

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

So converting the output to a tuple wouldn't break the API? A Version is a better decision than a tuple, but it's hard to tell how many people are counting on this API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, I have reverted Version to std::tuple as I did not know that one can compare them in such elegant way. I used to kill it if the code is not about generic programming (Scott Meyers book).
I learned something today, thanks!

@@ -426,7 +426,7 @@ namespace matplot {
run_command("set polar");
}
auto set_or_unset_axis = [this](class axis_type &ax,
std::string axis_name,
const std::string &axis_name,
Copy link
Owner

Choose a reason for hiding this comment

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

If that was your concern about breaking the API, both std::string axis_name and std::string const& axis_name are OK. In the implementation the std::string axis_name can be moved and the std::string const& axis_name can be copied. As far as the user is concerned, that doesn't functionally break the API. Only std::string &axis_name would be a problem.

Copy link
Contributor Author

@mikucionisaau mikucionisaau Oct 24, 2024

Choose a reason for hiding this comment

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

No concerns here: that is just a lambda expression - internal stuff.
And yes, it'll bind to the same calls as previously, we just need to make sure that the lambda expression itself does not modify the parameters, but that is checked by the compiler.

}
constexpr bool operator!=(const Version &other) const { return !(*this == other); }
constexpr bool operator<(const Version &other) const {
if (major < other.major)
Copy link
Owner

Choose a reason for hiding this comment

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

Is it the same as std::tie(major, minor, patch) < std::tie(other.major, other.minor, other.patch)?

Copy link
Owner

Choose a reason for hiding this comment

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

Yes. Looking at the rest of the code and considering tuple defines operator<, I do prefer Version over tuple here if we were coming up with the original API now, but I don't think breaking it now brings a lot of value compared to the potential hassle of people opening issues because the API changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is gone, sorry my bad.

axes_type::fplot(std::vector<function_line::function_type> equations,
std::array<double, 2> x_range,
std::vector<std::string> line_specs) {
axes_type::fplot(const std::vector<function_line::function_type> &equations,
Copy link
Owner

Choose a reason for hiding this comment

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

Same here. All these API changes are OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's OK if the thing is recompiled, but I am not sure at ABI level, and I do not know if anyone cares.

@mikucionisaau
Copy link
Contributor Author

I have removed my version mess as tuple is simpler (it aleady has all operators) and thus API is preserved.
I moved the code concerned with versioning into gnuplot class, which makes the code cleaner as the feature knowledge is maintained in one place.

Comment on lines -637 to -638
const bool has_wall_option =
std::get<0>(v) > 5 || (std::get<0>(v) == 5 && std::get<1>(v) >= 5);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was my trigger :-)

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