In most of the GC languages, but I mostly have a C# performance perspective, there are two "planes" to consider: the stack and the heap. In very rough, general terms, the heap is GCed and subject to its own world of memory/cpu than you are used to and the stack is has cache locality/access patterns similar to what you would expect from non-GCed languages. C# today in stack space especially increasingly looks a lot like a Rust relative if you squint: Memory<T> and Span<T> (and some relatives) live only on the stack and form a sort of borrow checking mechanism that is safer (including and especially to/from GCed objects on the heap) than raw pointer manipulation but directly related to it.

On the heap things obviously get more interesting and the rabbit hole can get quite deep. It's different from the unmanaged (non-GC) languages but similarities apply and eventually you start to map some common ground. Most GCs at this point are multi-generational and when you are talking about The Heap you are actually generally talking about a series of heaps (which is generally not just the name, but also vaguely resembles some of the internals of the data structure as well). In .NET these are generally called Gen 0, Gen 1, Gen 2, and the LOH (which may have multiple generations as well). Gen 0 is sometimes referred to as the "nursery" or the "short-lived generation", it's generally for fresh, new objects and gets garbage collected frequently. It can get quite fragmented and very chaotic. Objects that survive for long enough get promoted to Gen 1 and then again as they survive get promoted to Gen 2 which is meant for long-lived objects. Each generation is then in turn garbage collected with less frequency. The idea being that if an object in the heap appears to be long-lived it probably is. As that assumption holds (and it may not in non-well-behaved programs) the generations also get progressively less fragmented (higher cache locality in access patterns, in best cases).

LOH stands for Large Object Heap and when objects get above a certain (sometimes configurable) memory size they get treated differently. In some cases (older GCs, GCs running in lower memory environments) the regular Gen 2 doubles as the LOH, so large objects are "assumed" to be long-lived. In more modern/more recent/more common cases the LOH has its own Gen 0/Gen 1/Gen 2 breakdown with a similar garbage collection pattern of decreasing frequency with each generation.

A lot of the cache locality/access pattern balancing acts are figuring out the "best fit" heap and trying to optimize for that. You can build very performant apps with lots of small objects that preferably never escape the Gen 0 heap. Even though garbage collection is frequent it can be very fast with simple data structures (few to no reference cycles) and if most of the objects are tossed as garbage each collection the Gen 0 remains very chaotic, but not that fragmented. Most applications by default live in some version of this state, especially early in their development. Optimizing for it can sometimes be easy, too: figure out why references are living too long; often that means simplifying objects and removing reference cycles, and relying on data structures that work that way, which can be good refactors in general (often matching naive algorithm/logical performance analysis).

In other cases, you may want to focus on pushing your objects to the long-lived Gen 2 for benefits like increased cache locality and generally less fragmented access patterns. There's all sorts of patterns to do this including the dull simple ones like just holding on to references as long as possible, to things like object pools (instead of building new objects, hold on to references to "dead"/"unused" ones and reuse/recycle them as new again).

Knowing which kind of objects should never escape Gen 0 if you can help it and which ones should move all the way to Gen 2 can be a fascinating balance to strike.

The LOH obviously has similar concerns, but they all grow in complexity because those objects are large and potentially take up a lot of memory. If you are building a lot of "Gen 0" short-lived large objects, that might cause memory pressure issues. Having some idea of why those objects are so large and also getting garbage collected so quickly can help a lot to stabilize performance. But that also isn't a reason to avoid large objects. Some types of large objects can overall improve performance as they can be ways to more easily build cache locality or improve access patterns in cases like buffers and "memory pools", especially when long-lived in an application ("Gen 2 LOH").

As for intrinsics, they mostly apply to stack space, but with some Memory<T> and Span<T> options you can "borrow check" data from heap references. GCs obviously move things around (compaction to reduce fragmentation, collection to remove garbage and promote to the next generation) so the short-lived nature of stack space is interestingly important to their safety. Most of the intrinsics work with Vector<T>, a struct (stack space) which (among other options) can be constructed from any ReadOnlySpan<T> of the right size (and layout). That ReadOnlySpan<T> could be borrowed from an object somewhere in heap space (such as an intentional memory pool in Gen 2 LOH). No "copying" happens to build the Vector<T> because the ReadOnlySpan<T> is a type-safe/GC-safe pointer at that point and the Vector<T> can't last longer than the ReadOnlySpan<T>. Cache locality of that Vector<T> and subsequent ones in an operation become based on the data structure of your objects, but especially in the case of an intentional memory pool (an array or buffered series of arrays of some form) may be however you need it to be structured (arrays of value types like integers are designed to be contiguous memory and you can arrange things how you like in arrays, just like any other language; even if the GC affects where that array lives/moves, the data within it remains contiguous in the way you write it/read it, just about as you would expect).