It depends sincerely how you read memory.
You can use byte arrays to read a single 32-bit integer and read each offset separated and it will cause a huge overhead, because each p/invoke in .NET uses from 10 to 30 machines instructions (http://msdn.microsoft.com/en-us/library/ms235282.aspx in the "Performance Considerations") per call, then the less ReadProcessMemory call, the better.
On the other hand, you can still cache the structure to a byte array and convert yourself the offsets involved (commonly calling methods from BitConverter class). Since byte[] is considered by microsoft itself a blittable type, you haven't the overhead attached to Marshal the buffer. This way works really good related to performance.
Since IntPtr is a blittable type, it is, at least, as fast as the byte[] buffer approach, but I didn't need to make several conversions using BitConverter methods, and in thesis, I don't need to allocate an array (of bytes) in managed memory. Another point, it only uses a single DllImport and following Sketchy's idea, I provided a general way to read ANY struct, without the need to create a "Read_X_Struct", I posted the method "T Read<T>(int address)" and the "T[] ReadArray<T>(int address, int count)" which is really useful to map the memory like you do in unmanaged languagues like C++.
Remember that it was my personal tests, although it's a fast and reliable way to read structures from memory. You can use the same strategy to read battle list, tiles, everything in memory.
The only point which I'm really in caution is the fact it is using structs. You must take care when using structs when passing it to a function call, preferable to pass it by ref, avoiding the copy being made. For example, if you are dealing with a heavy structure like Creature (0xC0 bytes long) or Tile (0x148 bytes long), if you pass it to some method or even assign it to another field, you will cause the .NET copy all the data contained and pass the copy. If you do it several times, for sure you'll notice the performance hit.
EDIT: You gave me a good experiment to make based on my last paragraph. I'll do some tests using classes with a sequential layout to heavy data like Creature or Tile and using a DllImport passing an object as buffer, marshaling it as any.
EDIT2: Just noticed the functions "T Read<T>(int address)" and the "T[] ReadArray<T>(int address, int count)" still works for classes with StructLayout sequential. This might be the best choice for heavy data