Skip to content

Commit e190bbe

Browse files
Optimize string concatenation in loops: replace += with list.join() (#3246)
* Initial plan * Fix string concatenation performance issues in loops Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com> * Address code review feedback: Fix plugin list logic and add comment Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com> * Improve comment clarity for at_parts accumulation Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>
1 parent 92abc43 commit e190bbe

File tree

18 files changed

+151
-117
lines changed

18 files changed

+151
-117
lines changed

astrbot/core/db/migration/migra_3_to_4.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,15 @@ async def migration_persona_data(
250250
try:
251251
begin_dialogs = persona.get("begin_dialogs", [])
252252
mood_imitation_dialogs = persona.get("mood_imitation_dialogs", [])
253-
mood_prompt = ""
253+
parts = []
254254
user_turn = True
255255
for mood_dialog in mood_imitation_dialogs:
256256
if user_turn:
257-
mood_prompt += f"A: {mood_dialog}\n"
257+
parts.append(f"A: {mood_dialog}\n")
258258
else:
259-
mood_prompt += f"B: {mood_dialog}\n"
259+
parts.append(f"B: {mood_dialog}\n")
260260
user_turn = not user_turn
261+
mood_prompt = "".join(parts)
261262
system_prompt = persona.get("prompt", "")
262263
if mood_prompt:
263264
system_prompt += f"Here are few shots of dialogs, you need to imitate the tone of 'B' in the following dialogs to respond:\n {mood_prompt}"

astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ def check(self, content: str) -> tuple[bool, str]:
2121
if "data" not in res:
2222
return False, ""
2323
count = len(res["data"])
24-
info = f"百度审核服务发现 {count} 处违规:\n"
24+
parts = [f"百度审核服务发现 {count} 处违规:\n"]
2525
for i in res["data"]:
26-
info += f"{i['msg']}\n"
27-
info += "\n判断结果:" + res["conclusion"]
26+
parts.append(f"{i['msg']}\n")
27+
parts.append("\n判断结果:" + res["conclusion"])
28+
info = "".join(parts)
2829
return False, info

astrbot/core/pipeline/result_decorate/stage.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,13 @@ async def process(
246246
elif (
247247
result.use_t2i_ is None and self.ctx.astrbot_config["t2i"]
248248
) or result.use_t2i_:
249-
plain_str = ""
249+
parts = []
250250
for comp in result.chain:
251251
if isinstance(comp, Plain):
252-
plain_str += "\n\n" + comp.text
252+
parts.append("\n\n" + comp.text)
253253
else:
254254
break
255+
plain_str = "".join(parts)
255256
if plain_str and len(plain_str) > self.t2i_word_threshold:
256257
render_start = time.time()
257258
try:

astrbot/core/platform/astr_message_event.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,33 +91,34 @@ def get_message_str(self) -> str:
9191
return self.message_str
9292

9393
def _outline_chain(self, chain: list[BaseMessageComponent] | None) -> str:
94-
outline = ""
9594
if not chain:
96-
return outline
95+
return ""
96+
97+
parts = []
9798
for i in chain:
9899
if isinstance(i, Plain):
99-
outline += i.text
100+
parts.append(i.text)
100101
elif isinstance(i, Image):
101-
outline += "[图片]"
102+
parts.append("[图片]")
102103
elif isinstance(i, Face):
103-
outline += f"[表情:{i.id}]"
104+
parts.append(f"[表情:{i.id}]")
104105
elif isinstance(i, At):
105-
outline += f"[At:{i.qq}]"
106+
parts.append(f"[At:{i.qq}]")
106107
elif isinstance(i, AtAll):
107-
outline += "[At:全体成员]"
108+
parts.append("[At:全体成员]")
108109
elif isinstance(i, Forward):
109110
# 转发消息
110-
outline += "[转发消息]"
111+
parts.append("[转发消息]")
111112
elif isinstance(i, Reply):
112113
# 引用回复
113114
if i.message_str:
114-
outline += f"[引用消息({i.sender_nickname}: {i.message_str})]"
115+
parts.append(f"[引用消息({i.sender_nickname}: {i.message_str})]")
115116
else:
116-
outline += "[引用消息]"
117+
parts.append("[引用消息]")
117118
else:
118-
outline += f"[{i.type}]"
119-
outline += " "
120-
return outline
119+
parts.append(f"[{i.type}]")
120+
parts.append(" ")
121+
return "".join(parts)
121122

122123
def get_message_outline(self) -> str:
123124
"""获取消息概要。

astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ async def _convert_handle_message_event(
315315
abm.message.append(a)
316316
elif t == "at":
317317
first_at_self_processed = False
318+
# Accumulate @ mention text for efficient concatenation
319+
at_parts = []
318320

319321
for m in m_group:
320322
try:
@@ -354,13 +356,15 @@ async def _convert_handle_message_event(
354356
first_at_self_processed = True
355357
else:
356358
# 非第一个@机器人或@其他用户,添加到message_str
357-
message_str += f" @{nickname}({m['data']['qq']}) "
359+
at_parts.append(f" @{nickname}({m['data']['qq']}) ")
358360
else:
359361
abm.message.append(At(qq=str(m["data"]["qq"]), name=""))
360362
except ActionFailed as e:
361363
logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
362364
except BaseException as e:
363365
logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
366+
367+
message_str += "".join(at_parts)
364368
else:
365369
for m in m_group:
366370
a = ComponentTypes[t](**m["data"])

astrbot/core/platform/sources/discord/discord_platform_event.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,18 @@ async def _parse_to_discord(
113113
message: MessageChain,
114114
) -> tuple[str, list[discord.File], discord.ui.View | None, list[discord.Embed]]:
115115
"""将 MessageChain 解析为 Discord 发送所需的内容"""
116-
content = ""
116+
content_parts = []
117117
files = []
118118
view = None
119119
embeds = []
120120
reference_message_id = None
121121
for i in message.chain: # 遍历消息链
122122
if isinstance(i, Plain): # 如果是文字类型的
123-
content += i.text
123+
content_parts.append(i.text)
124124
elif isinstance(i, Reply):
125125
reference_message_id = i.id
126126
elif isinstance(i, At):
127-
content += f"<@{i.qq}>"
127+
content_parts.append(f"<@{i.qq}>")
128128
elif isinstance(i, Image):
129129
logger.debug(f"[Discord] 开始处理 Image 组件: {i}")
130130
try:
@@ -238,6 +238,7 @@ async def _parse_to_discord(
238238
else:
239239
logger.debug(f"[Discord] 忽略了不支持的消息组件: {i.type}")
240240

241+
content = "".join(content_parts)
241242
if len(content) > 2000:
242243
logger.warning("[Discord] 消息内容超过2000字符,将被截断。")
243244
content = content[:2000]

astrbot/core/platform/sources/slack/slack_adapter.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,39 +222,41 @@ def _parse_blocks(self, blocks: list) -> list:
222222
if element.get("type") == "rich_text_section":
223223
# 处理富文本段落
224224
section_elements = element.get("elements", [])
225-
text_content = ""
226-
225+
text_parts = []
227226
for section_element in section_elements:
228227
element_type = section_element.get("type", "")
229228

230229
if element_type == "text":
231230
# 普通文本
232-
text_content += section_element.get("text", "")
231+
text_parts.append(section_element.get("text", ""))
233232
elif element_type == "user":
234233
# @用户提及
235234
user_id = section_element.get("user_id", "")
236235
if user_id:
237236
# 将之前的文本内容先添加到组件中
237+
text_content = "".join(text_parts)
238238
if text_content.strip():
239239
message_components.append(
240240
Plain(text=text_content),
241241
)
242-
text_content = ""
242+
text_parts = []
243243
# 添加@提及组件
244244
message_components.append(At(qq=user_id, name=""))
245245
elif element_type == "channel":
246246
# #频道提及
247247
channel_id = section_element.get("channel_id", "")
248-
text_content += f"#{channel_id}"
248+
text_parts.append(f"#{channel_id}")
249249
elif element_type == "link":
250250
# 链接
251251
url = section_element.get("url", "")
252252
link_text = section_element.get("text", url)
253-
text_content += f"[{link_text}]({url})"
253+
text_parts.append(f"[{link_text}]({url})")
254254
elif element_type == "emoji":
255255
# 表情符号
256256
emoji_name = section_element.get("name", "")
257-
text_content += f":{emoji_name}:"
257+
text_parts.append(f":{emoji_name}:")
258+
259+
text_content = "".join(text_parts)
258260

259261
if text_content.strip():
260262
message_components.append(Plain(text=text_content))

astrbot/core/platform/sources/slack/slack_event.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,15 @@ async def send(self, message: MessageChain):
148148
)
149149
except Exception:
150150
# 如果块发送失败,尝试只发送文本
151-
fallback_text = ""
151+
parts = []
152152
for segment in message.chain:
153153
if isinstance(segment, Plain):
154-
fallback_text += segment.text
154+
parts.append(segment.text)
155155
elif isinstance(segment, File):
156-
fallback_text += f" [文件: {segment.name}] "
156+
parts.append(f" [文件: {segment.name}] ")
157157
elif isinstance(segment, Image):
158-
fallback_text += " [图片] "
158+
parts.append(" [图片] ")
159+
fallback_text = "".join(parts)
159160

160161
if self.get_group_id():
161162
await self.web_client.chat_postMessage(

astrbot/core/provider/sources/dashscope_source.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,15 @@ async def text_chat(
146146
# RAG 引用脚标格式化
147147
output_text = re.sub(r"<ref>\[(\d+)\]</ref>", r"[\1]", output_text)
148148
if self.output_reference and response.output.get("doc_references", None):
149-
ref_str = ""
149+
ref_parts = []
150150
for ref in response.output.get("doc_references", []) or []:
151151
ref_title = (
152152
ref.get("title", "")
153153
if ref.get("title")
154154
else ref.get("doc_name", "")
155155
)
156-
ref_str += f"{ref['index_id']}. {ref_title}\n"
156+
ref_parts.append(f"{ref['index_id']}. {ref_title}\n")
157+
ref_str = "".join(ref_parts)
157158
output_text += f"\n\n回答来源:\n{ref_str}"
158159

159160
llm_response = LLMResponse("assistant")

astrbot/core/star/filter/command.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ def __init__(
5151
self._cmpl_cmd_names: list | None = None
5252

5353
def print_types(self):
54-
result = ""
54+
parts = []
5555
for k, v in self.handler_params.items():
5656
if isinstance(v, type):
57-
result += f"{k}({v.__name__}),"
57+
parts.append(f"{k}({v.__name__}),")
5858
elif isinstance(v, types.UnionType) or typing.get_origin(v) is typing.Union:
59-
result += f"{k}({v}),"
59+
parts.append(f"{k}({v}),")
6060
else:
61-
result += f"{k}({type(v).__name__})={v},"
62-
result = result.rstrip(",")
61+
parts.append(f"{k}({type(v).__name__})={v},")
62+
result = "".join(parts).rstrip(",")
6363
return result
6464

6565
def init_handler_md(self, handle_md: StarHandlerMetadata):

0 commit comments

Comments
 (0)