[ITK] [ITK-dev] Fwd: Change in ITK[master]: BUG: Make valid calling with m_NumElements == 0

Luc Hermitte luc.hermitte at c-s.fr
Mon Oct 26 12:18:50 EDT 2015


Le 26/10/2015 16:24, David Cole a écrit :
> (cc-ing myself with my ITK dev mailing list email so I can loop in the
> ITK dev list so others may participate in the discussion if they feel so
> inclined)
>
>
> I think you must mean:
> std::fill(m_Data, m_NumElements, val)
>
> ...because if you really do mean m_Data->m_NumElements, you
> still have a problem when m_Data is nullptr.

Yes indeed, you're right. (It won't compile anyway. ^^')


> Same with:
> std::copy(&v.m_Data[0], &v.m_Data[N], &this->m_Data[0]);
>
> If m_Data can possibly be nullptr in either "v" or "this" then I do not
> see how this is a useful call to std::copy without checking for nullptr.
> If m_Data is nullptr, m_Data[0] and m_Data[N] are meaningless expressions.

They still mean m_Data+0 and m_Data+N. If N == 0, this is, IMO, somehow
a valid range, even is trivially empty. And copy() knows how to copy
empty ranges. We should not have to test whether the range is empty or not.

> I would really really really like to see some evidence from you that
> adding nullptr checks is materially harmful to performance in some real
> world fill/copy scenario before agreeing to any patch which does not
> include nullptr checks.

fill_n case should have already been fixed: I've used fill() instead to
force VC++ STL use preconditions expressed with a range instead of an
iterator.

Regarding the last case: copy().
I could return the question.
What's wrong with replacing the last and only invalid call to std::copy
(according to VC++ excessive checks) with:

  for (size_t i = 0; i != m_NumElements ; ++i)
    this->m_Data[i] = v.m_Data[i];
?

I agree that premature optimisation is a waste of time. Here, several of
you are suggesting the opposite: a premature pessimization, i.e. to do a
test that shall not be required if VC++ std::copy() preconditions were
correct.

Note BTW that I'm just suggesting to revert one change that I've
introduced in a previous patch
(http://review.source.kitware.com/#/c/19962/7/Modules/Core/Common/include/itkVariableLengthVector.hxx)


On a side note, I've submitted a bug report to VC++ tracker
(https://connect.microsoft.com/VisualStudio/feedback/details/1947236).


Regards
--Luc H.

>
>
> Thanks,
> David C.
>
>
>
> On Mon, Oct 26, 2015 at 10:48 AM, Luc Hermitte <luc.hermitte at c-s.fr
> <mailto:luc.hermitte at c-s.fr>> wrote:
>
>     After further investigations.
>
>     VC++'s fill() requires:
>     - the output range to be valid (i.e., in our case, if first!=last,
first
>     and last must be non null pointers, and first must be < last)
>
>     VC++'s fill_n() requires
>     - the output iterator to be valid, i.e. not null in case of a pointer
>
>     This means that by using std::fill(m_Data, m_Data->m_NumElements, val)
>     we should fix the bug observed.
>
>     I've embedded the fix in my latest patch set
>     (http://review.source.kitware.com/#/c/20253/)
>
>
>     Regarding std::copy().
>     VC++'s copy() requires :
>     - the input range to be valid (if first!=last, first and last
shall not
>     be null pointers, and first shall be < last)
>     - the output iterator to be valid (!=0 in case of a pointer)
>
>     There is a still bug with VC++ copy() implementation in the following
>     use case:
>       VariableLengthVector v1, v2;
>       v1 = v2; // should fail as well with VC++
>
>
>     Indeed, first SetSize(0, DontShrinkToFit(), DumpOldValue()) is called
>     -> no copy is done, but this->m_Data is left null.
>     Then, std::copy(&v.m_Data[0], &v.m_Data[N], &this->m_Data[0]);
>     Unfortunately, &this->m_Data[0] is left unchanged and null, and VC++
>     will complain :(
>
>
>     This means, either :
>     - we provide an itk::copy() that does not require outIt to be non null
>     when the input range is empty
>     - or we write the loop manually
>     - or we add another test in operator=(Self) -- thing that'll
really like
>     to avoid
>
>     Finally, note as well odd situations in the current implementation
-- my
>     fault I guess.
>     A newly default-constructed VLV will have a null m_Data pointer.
>     However, a VLV constructed with VariableLengthVector<type> v(0) will
>     have a non null m_Data pointer.
>
>     Regards
>     --Luc Hermitte
>
>     Le 26/10/2015 09:03, Luc Hermitte a écrit :
>     > Hi,
>     >
>     > Le 25/10/2015 18:37, Johnson, Hans J a écrit :
>     >> Should we simply add documentation that states:  "Behavior is
>     undefined when length of vector is zero”, add an assertion when
>     compiled in debug mode, and then remove the degenerate case in the
>     test suite?
>     >
>     >
>     > This could be enough regarding Fill(). I'd say it's up to you.
IMO, it
>     > could be simply fixed with an loop or an alternative of fill_n that
>     > states as precondition that "The range [outIt, outIt+n) shall be
>     valid ;
>     > and n shall be >= 0", instead of "The iterator outIt shall be
valid ;
>     > and n >= 0".
>     > This way the end-user won't have to check he doesn't call Fill() on
>     > empty VariableLengthVectors -- it would be considered as a no-op
>     exactly
>     > as "delete nullptr".
>     >
>     > However, we need to be sure that the copy constructor of empty
>     > VariableLengthVector has no side effect.
>     >
>     > Regards,
>     > --Luc Hermitte
>     >
>     >>
>     >>
>     >>
>     >>
>     >>
>     >> On 10/25/15, 8:49 AM, "Luc Hermitte (Code Review)"
>     <review at kitware.com <mailto:review at kitware.com>> wrote:
>     >>
>     >>> Luc Hermitte has posted comments on this change.
>     >>>
>     >>> Change subject: BUG: Make valid calling with m_NumElements == 0
>     >>>
>     ......................................................................
>     >>>
>     >>>
>     >>> Patch Set 2:
>     >>>
>     >>>> You cannot call std::fill_n with a nullptr for Debug VS 2013.
>     >>>>
>     >>>> The implementation of fill_n looks like this:
>     >>>>
>     >>>> template<class _OutIt,
>     >>>> class _Diff,
>     >>>> class _Ty> inline
>     >>>> _OutIt fill_n(_OutIt _Dest, _Diff _Count, const _Ty& _Val)
>     >>>> {  // copy _Val _Count times through [_Dest, ...)
>     >>>> _DEBUG_POINTER(_Dest);
>     >>>> return (_Fill_n(_Dest, _Count, _Val,
>     >>>> _Is_checked(_Dest)));
>     >>>> }
>     >>>>
>     >>>> The _DEBUG_POINTER line is called unconditionally, and will throw
>     >>>> this assert if the pointer is nullptr.
>     >>>
>     >>> That's odd. I don't read such a precondition in the standard.
>     >>> Is it the same with std::copy()?
>     >>>
>     >>>
>     >>>> Why are you against doing a nullptr check or a check on the
number
>     >>>> of elements prior to calling fill_n?
>     >>>>
>     >>>> If you are calling fill_n so frequently that you're concerned
about
>     >>>> adding a micro-smidge of time to each call, perhaps you're
calling
>     >>>> it too frequently...
>     >>>
>     >>> I totally agree that such algorithms are often called to many
>     times. and yet, when they are, they are called on pixels. This means
>     they are sometimes called billion times. We don't need to add a test
>     that we could easily avoid and still stay robust.
>     >>>
>     >>> What we need is a fill_n implementation that have as a
>     precondition: "ptr!=nullptr || size==0", and not "ptr!=nullptr".
>     >>>
>     >>>
>     >>>
>     >>>> If you want the fastest code possible, without doing the
>     >>>> appropriate nullptr safety checks, then you must guarantee
that the
>     >>>> entire test suite never tries to execute a test case with a
>     >>>> nullptr. So another solution would be simply NOT to execute tests
>     >>>> that result in these calls for Debug Microsoft builds.
>     >>>
>     >>> We simply need algorithms with less restrictive preconditions.
>     >>> For instance
>     >>>
>     >>>    // untested code
>     >>>    template <typename OutIt, typename Size, typename T>
>     >>>    OutIt itk::fill_n(OutIt start, Size size, T const& value) {
>     >>>        assert(start ||size == 0); // the test is not correct,
>     just to give an idea of what it should look like
>     >>>        for ( ; size != 0 ; --size, ++start)
>     >>>            *start = value;
>     >>>        return start;
>     >>>    }
>     >>>
>     >>>
>     >>>> It is my contention that **if** there is a performance
problem with
>     >>>> code after simply adding "if (this->m_Data)" checks before
fill and
>     >>>> copy calls, that the performance problem is with the
**callers** of
>     >>>> these things.
>     >>>
>     >>> Somehow I agree. Unfortunately, we tend to call these functions
>     too many times, i.e. once per pixel if not more. We have a lot of
>     pixels.
>     >>>
>     >>> Here it's easy to have a correct code (that'll pass tests with
>     VC++ in Debug Mode) without this redundant branching. Why pay for
>     this branching?
>     >>>
>     >>>
>     >>>> Fill and copy effectively perform N assignment operations. If you
>     >>>> cannot afford a single nullptr check when you are doing N
>     >>>> assignment operations then you should write special case code to
>     >>>> avoid the safety checks.
>     >>>
>     >>> N is often small (< 16)
>     >>>
>     >>>> The rest of us would like to be alerted ASAP when we accidentally
>     >>>> try to "fill" or "copy into" an empty container.
>     >>>
>     >>> We don't need to here.
>     >>> However, if you consider that filling or copying an empty vector
>     is a programming error, then we should document it and formalize the
>     related precondition (assertion, or C++17 [[expect: size != 0]]).

_______________________________________________
Powered by www.kitware.com

Visit other Kitware open-source projects at
http://www.kitware.com/opensource/opensource.html

Kitware offers ITK Training Courses, for more information visit:
http://kitware.com/products/protraining.php

Please keep messages on-topic and check the ITK FAQ at:
http://www.itk.org/Wiki/ITK_FAQ

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/insight-developers


More information about the Community mailing list