-
-
Notifications
You must be signed in to change notification settings - Fork 686
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
ENH: Fixed Coverity 2023.6.2 warnings in itkImageRegion.h #4334
Conversation
Thanks @issakomi. But although it's nice if such a warning can be avoided, it isn't really obvious to me that your PR would really improve the performance of |
const auto otherIndex = otherRegion.m_Index; | ||
const auto otherSize = otherRegion.m_Size; | ||
const auto & otherIndex = otherRegion.m_Index; | ||
const auto & otherSize = otherRegion.m_Size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both Index
and Size
are trivially copyable (having a super-fast compiler-generated copy-constructor and an empty compiler-generated destructor). When declaring otherIndex and otherSize as reference, each access to the data referred to by those variables (as in the for-loop below) will have an extra indirection. Both otherIndex[i] and otherSize[i] are accessed multiple times. That's why I wonder: is this really a performance improvement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood this as a change to silence a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, otherRegion object is already passed by constant reference to the function. Not sure that there are some "extra indirection" costs for otherIndex and otherSize, IMHO they are just aliases, but maybe you are right. Just close the PR if you think there are some costs for "extra indirection" of constant reference.
Coverity 2023.6.2: 275 bool 276 IsInside(const Self & otherRegion) const 277 { >>> CID 331225: Performance inefficiencies (AUTO_CAUSES_COPY) >>> Using the "auto" keyword without an "&" causes the copy of an object of type "itk::ImageRegion<3u>::IndexType". 278 const auto otherIndex = otherRegion.m_Index; 275 bool 276 IsInside(const Self & otherRegion) const 277 { 278 const auto otherIndex = otherRegion.m_Index; >>> CID 331206: Performance inefficiencies (AUTO_CAUSES_COPY) >>> Using the "auto" keyword without an "&" causes the copy of an object of type "itk::ImageRegion<3u>::SizeType". 279 const auto otherSize = otherRegion.m_Size;
Thanks for adjusting the commit message, @issakomi Of course it would have been nice if the commit would yield a significant performance improvement, but I still doubt if it would. It does obviously fix the CID 331225 (AUTO_CAUSES_COPY) warning, although I feel that the warning is undeserved for types that are trivially copyable. Another way to address CID 331225 (AUTO_CAUSES_COPY) might be to just not use
Would you also like that? Anyway, I'm kinda neutral to the PR now. But generally speaking, I think it's OK to copy a trivially copyable object, instead of using a const-reference to the object. Copying (rather than referencing) will avoid an extra indirection when accessing the object. Moreover, it will ease making code thread-safe. (Consider two threads referring to one and the same object versus each thread having its own copy of the object.) |
If you re-open this PR, I could merge it. Assuming you don't create a new PR, which removes the |
I have done some tests. Even if this PR is not important for me at all, it is just a warning from Coverity and and I don't heavily use this IsInside function in my app. I will not re-open the PR.
It depends on dimension of the itk::ImageRegion. The larger the dimension, the greater the difference, of course copy takes more time. I have done some tests with dimensions 3 and 10. With
I am afraid that thread-safety argument is not applicable in particular case, otherRegion is passed by const-reference to the function. Here is the test. But it is a little tricky, the times vary, the test have to be run many times to get an idea (and it would be better to prepare different binaries before testing). 10000 IsInside calls: With dim 10 and With dim 10 and With dim 3 and With dim 3 and #include <itkImageRegion.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <random>
int main(int, char **)
{
///////////////////////////////////////////////////////////////////////////////
//
//
//
//
constexpr unsigned int dim = 10;
constexpr unsigned int num_tests = 10000;
//
//
//
//
///////////////////////////////////////////////////////////////////////////////
using RegionType = itk::ImageRegion<dim>;
using IndexType = RegionType::IndexType;
using SizeType = RegionType::SizeType;
RegionType region;
{
SizeType size;
for (unsigned int x = 0; x < dim; ++x)
{
size[x] = 2048;
}
IndexType index{};
region.SetSize(size);
region.SetIndex(index);
}
std::vector<RegionType> regions;
for (unsigned int x{}; x < num_tests; ++x)
{
RegionType r;
SizeType s;
IndexType i{};
for (unsigned int k{}; k < dim; ++k)
{
s[k] = x + 1;
}
r.SetSize(s);
r.SetIndex(i);
regions.push_back(r);
}
std::vector<bool> results;
///////////////////////////////////////////////////////////////////////////////
//
//
//
//
const auto t0 = std::chrono::steady_clock::now();
for (size_t x{}; x < regions.size(); ++x)
{
const bool k = region.IsInside(regions.at(x));
results.push_back(k);
}
const auto t1 = std::chrono::steady_clock::now();
const auto ts = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0);
//
//
//
//
///////////////////////////////////////////////////////////////////////////////
// To avoid "optimized away" scenario
{
const unsigned long long seed =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::mt19937_64 mtrand(seed);
const unsigned int x = mtrand() % num_tests;
std::cout << x << " " << static_cast<int>(results.at(x)) << std::endl;
}
std::cout << "Time: " << ts.count() << " ns" <<std::endl;
return 0;
} Edit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for further investigating this topic, @issakomi I think you convinced me that your proposed change is a proper way to address the Coverity warnings, at least in this particular case. In general I think we should still allow code of the form const auto x = expression
in our code base (for the reasons I mentioned before), so I hope this is a special (exceptional) case. Anyway, approved 👍
By the way, I think I will still ask around what other C++ programmers think about those For the record, Coverity 2023.6.0 Release Notes claims that:
@issakomi Can you possibly figure out for us what Coverity means by "small objects", in this context? Apparently |
@issakomi Can you possibly still figure out whether the AUTO_CAUSES_COPY warning appears on both The Coverity warning supposedly does not appear for "small objects", so I'm just curious now where they draw the line between small and large! |
The warning was about 3D region only, I am not sure about the limit for "small objects", maybe 8 or 10 bytes or something, I could not find information about the limit, only these lines in "2023.6.0 Release Notes"
|
Thanks @issakomi Interesting! But then, maybe they might also consider the counter-part:
Might then produce a warning, saying "Using |
@issakomi Can you possibly still check whether or not the AUTO_CAUSES_COPY warning appears when an object is small (at most 8 bytes) but "non-trivial"? Like the following
|
I have put following code in my app: {
using RegionType2 = itk::ImageRegion<2>;
RegionType2 region2{};
const auto otherSize2 = region2.GetSize();
std::cout << otherSize2[0] << " " << otherSize2[1] << std::endl;
}
{
using RegionType3 = itk::ImageRegion<3>;
RegionType3 region3{};
const auto otherSize3 = region3.GetSize();
std::cout << otherSize3[0] << " " << otherSize3[1] << " " << otherSize3[2] << std::endl;
} This triggered one defect:
There is also documentation for every "checker", but nasty additional registration at "Synopsys community" seems to be required (or I didn't find how else to see it). I can check your String code later, the number of scans per days is limited. |
@N-Dekker I can run a scan today, it is OK to add the code like in
#include <iostream>
#include <memory>
#include <cstring>
class String
{
public:
String() {}
String(const String& arg)
:
m_ptr(new char[strlen(arg.m_ptr.get()) + 1])
{
strcpy(m_ptr.get(), arg.m_ptr.get());
}
std::unique_ptr<char[]> m_ptr;
};
void Check(const String& arg)
{
const auto copyOfString = arg;
std::cout << copyOfString.m_ptr.get() << std::endl;
}
int main()
{
String s;
s.m_ptr = std::unique_ptr<char[]>(new char[4]{'a','b','c','\0'});
Check(s);
return 0;
} |
Done.
Also |
Thanks @issakomi So you mean that there is no warning on |
Yes. I guess Edit: #include <iostream>
#include <memory>
#include <type_traits>
class String
{
public:
std::unique_ptr<char[]> m_ptr;
};
class StringSimple
{
public:
char * m_ptr;
};
int main()
{
std::cout << std::is_trivial<String>() << '\n'
<< std::is_trivial<StringSimple>() << std::endl;
return 0;
}
|
Thanks again. In this case, I think Regarding your Edit:
OK, but then it would no longer be copyable, so |
Yes. Next time I'll try to use a huge |
I have done this crazy test with
|
Coverity 2023.6.2: