discord.py:如何提升服务器最早/最晚创建用户查询命令的处理速度
Hey there! Let's break down why your commands are running slow and fix them step by step. The core issue here is the O(n²) time complexity of your current code—for every member in the server, you're looping through all members again to calculate their position. On a 2000-user server, that's 4 million operations, which is way too slow. Plus, since Discord.py runs on a single-threaded event loop, this blocking operation stops your bot from responding to anything else until it's done.
1. Replace the O(n²) Position Calculation with a Single Sort
Instead of calculating each member's position by looping through everyone every time, just sort the member list once by created_at, then grab the page you need directly. This drops your time complexity to O(n log n), which is way faster even for large servers.
2. Offload Heavy Work to a Thread (Avoid Blocking the Bot)
Even with sorting, if your server grows to 10k+ users, the sort might still block the event loop for a moment. Using asyncio.to_thread (available in Python 3.9+) lets you run the sorting in a separate thread, keeping your bot responsive to other commands while the work happens.
3. Cache the Sorted List (Avoid Repeating Work)
If users run oldest or newest multiple times in a short period, there's no need to re-sort the list every time. You can cache the sorted results with a timeout (like 5 minutes) so subsequent calls use the cached data instead of reprocessing everything.
Here's the optimized version of your code incorporating all these fixes:
import asyncio from datetime import datetime, timedelta import discord from discord.ext import commands class UserSort(commands.Cog): def __init__(self, client): self.client = client self._cache = { "oldest": {"data": None, "expires": None}, "newest": {"data": None, "expires": None} } async def _get_sorted_members(self, guild, sort_type): # Check if we have a valid cached result cache_entry = self._cache[sort_type.lower()] now = datetime.utcnow() if cache_entry["data"] is not None and cache_entry["expires"] > now: return cache_entry["data"] # Offload sorting to a thread to avoid blocking the event loop def sort_members(): # Filter out members with no created_at (should be rare) valid_members = [m for m in guild.members if m.created_at is not None] # Sort based on type if sort_type == "Oldest": return sorted(valid_members, key=lambda x: x.created_at) else: return sorted(valid_members, key=lambda x: x.created_at, reverse=True) sorted_members = await asyncio.to_thread(sort_members) # Update cache with 5-minute expiration cache_entry["data"] = sorted_members cache_entry["expires"] = now + timedelta(minutes=5) return sorted_members @commands.command() async def oldest(self, ctx, page: int = 1): await self._display_sorted_page(ctx, page, "Oldest") @commands.command() async def newest(self, ctx, page: int = 1): await self._display_sorted_page(ctx, page, "Newest") async def _display_sorted_page(self, ctx, page, sort_type): page = abs(page) max_page = 99 if page > max_page: page = max_page # Get sorted members (from cache or fresh sort) sorted_members = await self._get_sorted_members(ctx.guild, sort_type) total_members = len(sorted_members) # Calculate page boundaries per_page = 10 start_idx = (page - 1) * per_page end_idx = start_idx + per_page page_members = sorted_members[start_idx:end_idx] # Build the output string output_string = "" for idx, member in enumerate(page_members, start=start_idx + 1): output_string += f"**{idx}** - {member} - {member.created_at.strftime('%d/%m/%Y')}\n" # Handle empty page (if page is beyond total members) if not output_string: output_string = "No members found on this page!" # Create embed em = discord.Embed(color=self.client.Blue) em.add_field( name=f"{sort_type} accounts in **{ctx.guild.name}**", value=output_string, inline=False ) em.set_footer(text=f"Page: {page} | Total members: {total_members}") em.timestamp = datetime.utcnow() await ctx.send(embed=em) # Don't forget to add the cog to your bot # client.add_cog(UserSort(client))
Let's go over what's changed:
- Sorting once instead of per member: We sort the list once and then slice it for the desired page, which is way more efficient.
- Threaded sorting:
asyncio.to_threadruns the sorting in a background thread, so your bot can still respond to other commands while this work is done. - Caching: The sorted list is stored for 5 minutes, so repeated calls don't re-sort the entire member list every time.
- Cleaner page handling: Using list slicing (
sorted_members[start_idx:end_idx]) simplifies grabbing the right page of members instead of tracking 10 separate variables.
内容的提问来源于stack exchange,提问作者user16176324

