I'll give you a list of pitfalls I've ran into on my current project.
But I'll also caution you not to worry too much about performance until it becomes an issue for you. You want to make something, first and foremost. And a lot of developers just getting into games tend to severely underestimate the amount of work their processors can do before it becomes an issue.
The most easy two to fall into are improper draw call management (requests made to the GPU to draw an item), and not accounting for the engine's lack of native occlusion culling (it ends up drawing items obstructed from view by nearer or larger objects).
The first is an easy fix. The engine draws objects using two object-types: MeshInstance, and MultimeshInstance. For each MeshInstance you have, you're going to have a single draw call to the GPU, and the overhead of that really adds up in more complicated scenes. It's generally a good idea to batch multiple objects with the same display geometry into a MultimeshInstance, which will send a single call to the GPU with a list of transforms to assign to that specific object. This minimizes the communication overhead between the CPU and the GPU.
The problem with this is that the MultimeshInstance has (to my knowledge) no in-editor tool for managing its transforms. It has to be done through a script.
For the time being, I've written a script that enumerates all MeshInstances in my scene that share a material and display geometry, and assigns them to a set of evenly spaced MultimeshInstances based on their position in space (this prevents the GPU from attempting to render too many instance elements that are off-screen).
Occlusion culling is trickier, and outside of manually setting your own visibility lists for objects, and deactivating them via script at runtime when they're out of sight, there's not too much to be done other than to keep overdraw in the back of your mind right now.
Less specific to Godot, there are a number of optimizations you can make that work well in any engine.
- LOD (level of detail) geometry:
Godot doesn't natively support object LODs (different resolution versions of objects displayed at difference distances). This can be rectified fairly trivially by toggling different MeshInstances at different distances from your parent object, and can go a long way to reducing the amount of rendering overhead in your scene.
- Shadow casters are expensive:
If you're got a light that casts a realtime shadow in your scene, you might as well be rendering the scene twice (especially in Godot, since there's no occlusion culling). Even if you don't have a LOD system in place, I recommend using a lower-resolution version of your scene to cast shadows, and even disabling shadow-casting on smaller objects. This can be done by adjusting the visibility mask in your shadow caster.
- Minimize Texture Sampling:
If you're writing your own shaders, keep texture lookups to a minimum, especially on large objects.
- Adjust the physics tick:
The physics simulations in most modern game engines run at a fixed rate, usually somewhere between 30 & 60hz. This is (mostly) decoupled from the game's frame rate. Godot lets you set the update frequency from its project settings, and I recommend you play with that number until you find the minimum value that works for your title.
- Stagger object updates:
If you can get away with running an update less frequently than other elements in your game, do that. A good example is running the decision loop for your AI every nth game tick, or every nth second. Batch your AI into n groups, so you end up doing 1/n amount of the processing you otherwise might have been doing per-frame.
- Try to offload purely visual things to the GPU if at all possible:
I've seen a few people writing scripts to add real simple motions to visual elements that weren't directly related to the game's simulation. A good example would be a quest marker bobbing up/down above a character's head. This is something that could easily be done on the GPU using the vertex function of a shader, and represents an absolute waste of CPU time.
- If you can get away with it, lower the visual fidelity in your project settings:
Pretty self-explanatory. Especially when editing your project, you'll experience a much lower (nearly half) the frame rate you'll see at deployment at runtime. It's to be expected, you're running debug code, and an editor. Disable some of the extra visual features if they're only icing on the cake of your game's aesthetics so you can get a better idea of your title's actual performance.