I've been asked how i go about profiling, but i think the best explanation i could possibly give would be to look at this (very interesting) video.
Frederico talking at Fosdem
Other than that, all i can say is Reduce, Reuse and Recycle.
1) Reduce: Every object you allocate is an object the GC has to clean up. Know how many objects you're allocating and reduce the number if it's excessive. For example i once was calling DateTime.Now several thousand times a second because of the loop's i had written. By moving the call before the loops, i reduced the allocations from that by a factor of several thousand.
2) Reuse/Recycle: I used to allocate a new byte array every time i wanted to send a message to another computer via a socket or receive a message from them. This resulted in a *lot* of 16kB byte array's floating around. Instead i created a BufferManager class. From this class i requested a buffer every time i needed one and then returned the buffer back to the manager every time i was finished with it. This way i never allocated more buffers than i needed (about 40-60 buffers typically). Previously i could be allocating as much as 40 new buffers each and every second.
I might blog more about that later if people really want me to. But that all depends on how much people beg :P