By this point you should have optimized your environment and the next step is to look at web part coding. I’ve noticed that most people write web parts for SharePoint in a very similar style to server controls and put most of the work in the RenderWebPart() method. For small sites this tends to work well but unfortunately, this doesn’t tend to scale well. The secret to well performing web parts is to utilize the secondary worker thread. When SharePoint is building a page we can utilize this secondary worker thread along with our own delegate to do all the pre-processing work for all the webparts in parallel before rendering.
Basic support for this model is built into the base WebPart class and to utilize it we will be overriding two methods. GetData() and GetRequiresData(). The best introduction I have found on this is in the document A Web Part's journey through Asynchronous City
Instead of starting from scratch, I highly recommend using the PortalBuilder WebParts Application Block. It comes with a BaseWebPart class that provides error handling and asynch support to Microsoft’s WebPart class. Once you are using the PortalBuilder class you only need to implement the LoadData() method.
It is worth mentioning that you can do quite a bit more in the LoadData() method than simply get data. For example, if you are building a hierarchical structure (like a tree control) you can build the entire control asynchronously and then render the finished product in your RenderWebPart() method.