Why does NOLOCK make a scan with variable assignment slower?
NOTE: this might not be the type of answer you're looking for. But perhaps it will be helpful to other potential answerers as far as providing clues as where to start looking
When I run these queries under ETW tracing (using PerfView), I get the following results:
Plain  - 608 ms  
NOLOCK - 659 ms

So the difference is 51ms.  This is pretty dead on with your difference (~50ms).  My numbers are slightly higher overall because of the profiler sampling overhead.
Finding the difference
Here's a side-by-side comparison showing that the 51ms difference is in the FetchNextRow method in sqlmin.dll:

Plain select is on the left at 332 ms, while the nolock version is on the right at 383 (51ms longer).  You can also see that the two code paths differ in this way:

Plain SELECT

sqlmin!RowsetNewSS::FetchNextRow calls

sqlmin!IndexDataSetSession::GetNextRowValuesInternal




Using NOLOCK

 sqlmin!RowsetNewSS::FetchNextRow calls

sqlmin!DatasetSession::GetNextRowValuesNoLock which calls either

sqlmin!IndexDataSetSession::GetNextRowValuesInternal or
kernel32!TlsGetValue







This shows that there is some branching in the FetchNextRow method based on the isolation level / nolock hint.
Why does the NOLOCK branch take longer?
The nolock branch  actually spends less time calling into GetNextRowValuesInternal (25ms less).  But the code directly in GetNextRowValuesNoLock (not including methods it calls AKA the &quot;Exc&quot; column) runs for 63ms - which is the majority of the difference (63 - 25 = 38ms net increase in CPU time).
So what's the other 13ms (51ms total - 38ms accounted for so far) of overhead in FetchNextRow?
Interface dispatch
I thought this was more of a curiosity than anything, but the nolock version appears to incur some interface dispatch overhead by calling into the Windows API method kernel32!TlsGetValue via kernel32!TlsGetValueStub - a total of 17ms.  The plain select appears to not go through the interface, so it never hits the stub, and only spends 6ms on TlsGetValue (a difference of 11ms).  You can see this above in the first screenshot.
I should probably run this trace again with more iterations of the query, I think there are some small things, like hardware interrupts, that were not picked up by the 1ms sample rate of PerfView

Outside of that method, I noticed another small difference that causes the nolock version to run slower:
Releasing Locks
The nolock branch appears to more aggressively run the  sqlmin!RowsetNewSS::ReleaseRows method, which you can see in this screenshot:

The plain select is on top, at 12ms, while the nolock version is on the bottom at 26ms (14ms longer).  You can also see in the &quot;When&quot; column that the code was executed more frequently during the sample.  This may be an implementation detail of nolock, but it seems to introduce quite a bit of overhead for small samples.

There are lots of other small differences, but those are the big chunks.