Not inlined?

This is a discussion on Not inlined? within the C++ Programming forums, part of the General Programming Boards category; I'm curious, because I don't understand why VC refuses to inline this function: Code: template < typename T, bool UNDERFLOW_T, ...

  1. #1
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,536

    Not inlined?

    I'm curious, because I don't understand why VC refuses to inline this function:
    Code:
    template<typename T, bool UNDERFLOW_T, bool OVERFLOW_T, bool THROW_T,
    bool LOGGING_T, T UPPER_T, T LOWER_T>
    CInt<T, UNDERFLOW_T, OVERFLOW_T, THROW_T, LOGGING_T, UPPER_T, LOWER_T>
    CInt<T, UNDERFLOW_T, OVERFLOW_T, THROW_T, LOGGING_T, UPPER_T, LOWER_T>::
    operator + (const CInt& Data)
    {
    	//if (bOverflow && m_Data + Data.m_Data > boost::integer_traits<T>::const_max)
    	T tmp = m_Data + Data.m_Data;
    	if (OVERFLOW_T)
    	{
    		//T UpperRange = UPPER_T / 2;
    		//T NewValue = m_Data / 2 + Data.m_Data / 2;
    		//T Carry = (m_Data & Data.m_Data & 1);
    		//T Tmp = NewValue + Carry;
    		//if (UpperRange < Tmp)
    		if (tmp < m_Data && Data.m_Data > 0)
    		{
    			if (THROW_T)
    				throw std::overflow_error("");
    			else
    				set_overflow();
    		}
    	}
    	//if ( UNDERFLOW_T && m_Data + Data.m_Data < boost::integer_traits<T>::const_min)
    	if (UNDERFLOW_T)
    	{
    		T LowerRange = LOWER_T / 2;
    		T NewValue = m_Data / 2 + Data.m_Data / 2;
    		T Carry = (m_Data ^ Data.m_Data) & 1;
    		T Tmp = NewValue - Carry;
    		if (LowerRange > Tmp)
    			throw std::underflow_error("");
    	}
    	return tmp;
    }
    Although, the compiler seems to have inlined this implementation:
    Code:
    template<typename T, bool UNDERFLOW_T, bool OVERFLOW_T, bool THROW_T,
    bool LOGGING_T, T UPPER_T, T LOWER_T>
    CInt<T, UNDERFLOW_T, OVERFLOW_T, THROW_T, LOGGING_T, UPPER_T, LOWER_T>
    CInt<T, UNDERFLOW_T, OVERFLOW_T, THROW_T, LOGGING_T, UPPER_T, LOWER_T>::
    operator + (const CInt& Data)
    {
    	T tmp = m_Data + Data.m_Data;
    	if (OVERFLOW_T || UNDERFLOW_T)
    	{
    		if (tmp < m_Data && OVERFLOW_T && Data.m_Data > 0)
    			set_overflow();
    		if (tmp > m_Data && UNDERFLOW_T && Data.m_Data < 0)
    			set_underflow();
    	}
    	return tmp;
    }
    Does anyone think they can guess as to why?
    Removing the overflow/underflow checking, it just performs an addition operation with 4 instructions.
    Even with overflow/underflow checking, I don't see why it can't inline.
    Last edited by Elysia; 06-01-2008 at 03:28 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  2. #2
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,893
    Probably because it's just too complex for the compiler's taste. If the inlining pass happens before constant propagation and dead code elimination (which is a good idea, because then you can optimize the inlined version based on the actual parameters), then those are very complex functions. And the first is a little more complex than the second, probably crossing some heuristic line.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  3. #3
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,299
    Wouldn't you first want to put an "if (THROW_T)" in the underflow detection case, and simplify the underflow detection to just "if (tmp > m_Data && Data.m_Data < 0)" first, before you worry about whether it's inlined or not?
    Oh I guess you're worried about undefined behaviour with regards to underflow? Overflow with signed numbers is just as undefined though. It's overflow with unsigned numbers that is well defined, iirc.

    You haven't stated what the template parameters are when it refuses to inline. Does it not inline when they are all false? You didn't say what the type T was either.
    I'm guessing you get a lot of "Conditional expression is constant" warnings at the highest warning level?
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  4. #4
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,536
    Quote Originally Posted by iMalc View Post
    Wouldn't you first want to put an "if (THROW_T)" in the underflow detection case, and simplify the underflow detection to just "if (tmp > m_Data && Data.m_Data < 0)" first, before you worry about whether it's inlined or not?
    Maybe. I was testing code at the time, and was wondering why the compiler couldn't inline it because it looked so simple.

    Oh I guess you're worried about undefined behaviour with regards to underflow? Overflow with signed numbers is just as undefined though. It's overflow with unsigned numbers that is well defined, iirc.
    Not really worried... They'll just wrap around if I'm not mistaken.

    You haven't stated what the template parameters are when it refuses to inline. Does it not inline when they are all false?
    Nope, it didn't do that either, even when they were all false.

    You didn't say what the type T was either.
    T was uint8_t or int8_t, or maybe uint64_t, I think. That's the types I was testing with.

    I'm guessing you get a lot of "Conditional expression is constant" warnings at the highest warning level?
    No, I get no warnings at all at maximum level. I always use highest level warnings.
    That's the beauty. Since the templates parameters are known at compile time, then compile can optimize away code instead of having to generate a runtime check and slowing down performance.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  5. #5
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,299
    Quote Originally Posted by Elysia View Post
    Maybe. I was testing code at the time, and was wondering why the compiler couldn't inline it because it looked so simple.


    Not really worried... They'll just wrap around if I'm not mistaken.


    Nope, it didn't do that either, even when they were all false.


    T was uint8_t or int8_t, or maybe uint64_t, I think. That's the types I was testing with.


    No, I get no warnings at all at maximum level. I always use highest level warnings.
    That's the beauty. Since the templates parameters are known at compile time, then compile can optimize away code instead of having to generate a runtime check and slowing down performance.
    It's not that the compiler "couldn't" inline it, it's that it chose not to. It either knows there is little to be gained (and it is often right), or your compiler settings didn't allow it.

    The values wont necessarily "wrap around" on all platforms if I'm not mistaken. Some may clip at the min or max value.
    I'm pretty sure that unsigned behaviour is specified to wrap around though.

    When they're all false, then the code is essentially this:
    Code:
    template<typename T> CInt<T> CInt<T>::operator + (const CInt& Data)
    {
    	T tmp = m_Data + Data.m_Data;
    	return tmp;
    }
    Note that you should use the two-parameter version for operator +

    If it wont inline that, then perhaps it's to do with the construction of the CInt return value from T. That part is probably getting inlined, and the current inlining level probably prevents the resulting code containing that inlining from itself also being inlined.
    Why don't you declare tmp as a CInt, assign to tmp.m_Data and return tmp instead? This allows for NRVO too.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  6. #6
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,536
    Quote Originally Posted by iMalc View Post
    It's not that the compiler "couldn't" inline it, it's that it chose not to. It either knows there is little to be gained (and it is often right), or your compiler settings didn't allow it.
    Of course. This is always the case, isn't it?
    Nothing wrong with the settings, though. It's just the compiler didn't chose to do it.

    The values wont necessarily "wrap around" on all platforms if I'm not mistaken. Some may clip at the min or max value.
    I'm pretty sure that unsigned behaviour is specified to wrap around though.
    Now that's a shame, because as I'm implementing range bounds for integers, I'm emulating the "wrap around" behavior as they overflow or underflow.

    Note that you should use the two-parameter version for operator +
    Free operators are always the best, I know
    It's a member function right now, but I'll be breaking it out soon enough.

    If it wont inline that, then perhaps it's to do with the construction of the CInt return value from T. That part is probably getting inlined, and the current inlining level probably prevents the resulting code containing that inlining from itself also being inlined.
    Why don't you declare tmp as a CInt, assign to tmp.m_Data and return tmp instead? This allows for NRVO too.
    Let's not get too ahead of ourselves just yet, though. There's still work to be done and testing. I think I'm finished with the new operator +.
    Let's see if it will inline and possible optimizations for that one.


    UPDATE:
    My newest code for operator + is:
    Code:
    template<typename T, template<typename T> class Traits>
    T CInt<T, Traits>::AddChecks(const CInt& Data) const
    {
    	// No range checking or overflow checks to be made - just return data
    	bool bNoRangeCheck = (Traits<T>::RangeUpper == boost::integer_traits<T>::const_max &&
    		Traits<T>::RangeLower == boost::integer_traits<T>::const_min);
    	if (!Traits<T>::bOverflow && !Traits<T>::bUnderflow && bNoRangeCheck)
    		return m_Data + Data.m_Data;
    	//if (Traits<T>::bOverflow || Traits<T>::bUnderflow)
    	{
    		//T tmp = Traits<T>::RangeUpper - m_Data; //m_Data + Data.m_Data;
    		//if (tmp < m_Data && Traits<T>::bOverflow && Data.m_Data > 0)
    		if (/*Traits<T>::bOverflow && */Data.m_Data > 0 && Data.m_Data >
    			(Traits<T>::RangeUpper - m_Data))
    		{
    			if (Traits<T>::bOverflow) set_overflow();
    			if (bNoRangeCheck) 
    				return m_Data + Data.m_Data;
    			else
    				return ( Data.m_Data - (Traits<T>::RangeUpper - m_Data) );
    		}
    		else if (/*Traits<T>::bUnderflow && */Data.m_Data < 0 && -Data.m_Data >
    			(m_Data - Traits<T>::RangeLower))
    		{
    			if (Traits<T>::bUnderflow) set_underflow();
    			if (bNoRangeCheck) 
    				return m_Data + Data.m_Data;
    			else
    				return ( Traits<T>::RangeUpper -
    					(-Data.m_Data - (m_Data - Traits<T>::RangeLower) - 1) );
    		}
    	}
    	// No overflow or underflow, but range checking is enabled
    	return m_Data + Data.m_Data;
    }
    It seems it's inlined because the compiler just optimizes away it all.

    Code:
    	srand( time(NULL) );
    	integer = rand();
    	integer += -1;
    	integer.resetflags();
    	integer += -1;
    	integer += -60000;
    	integer += -60000;
    	cout << integer.underflowed() << endl;
    	cout << (int)integer.get() << endl;
    	integer.resetflags();
    	integer += 60000;
    	integer += 60000;
    	integer += 1;
    	cout << integer.overflowed() << endl;
    	cout << (int)integer.get();
    Ah, I love the compiler. It doesn't do any overflow/underflow checking unless I actually check if there was an underflow or overflow (remove the .underflowed/.overflowed calls)!
    Plus it is all inlined. I'm proud of my compiler today

    Range checking is also optimized and inline, and it applies a darn good algorithm for the range checking.
    No tricky calculations - it just does it. Wonderful optimizations I say.
    Last edited by Elysia; 06-02-2008 at 04:04 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. unfamiliar syntax
    By bean66 in forum C++ Programming
    Replies: 11
    Last Post: 04-02-2009, 12:36 PM
  2. Best way to find out if a function got inlined
    By pheres in forum Windows Programming
    Replies: 4
    Last Post: 03-28-2009, 03:54 AM
  3. When to inline your *tors
    By Angus in forum C++ Programming
    Replies: 43
    Last Post: 10-29-2008, 03:38 PM
  4. When to use a .cpp file
    By philvaira in forum C++ Programming
    Replies: 29
    Last Post: 07-26-2007, 10:11 AM
  5. inlined and outlined member functions
    By Drake in forum C++ Programming
    Replies: 2
    Last Post: 07-26-2006, 08:10 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21