甲醛是什么气味| 肾炎的症状是什么| qrs是什么意思| 什么是天珠| 院士相当于什么级别| 心脏房颤是什么症状| 白带是绿色的是什么原因| 十月一日是什么星座| 821是什么意思| 虾仁炒什么好吃| 丙火是什么意思| 温州什么最出名| 甲状腺低回声结节是什么意思| 一月十八号是什么星座| 淑女气质给人什么感觉| 瓤是什么意思| 江西简称是什么| 什么是物理学| 虾仁炒什么好吃又简单| SEX是什么| 现在买什么股票好| 一什么眼镜| 下过海是什么意思| 白细胞高有什么危害| 红鸡蛋用什么染| 上网是什么意思| 阴超能检查出什么| 樵夫是什么生肖| 竹叶青属于什么茶| 自强不息的息是什么意思| 小孩嗓子疼吃什么药| 梦见钓到大鱼是什么意思| 智齿拔了有什么影响| 十一月四日是什么星座| 塞翁失马是什么意思| 梦见修坟墓是什么预兆| 行代表什么生肖| 三个降号是什么调| 绿字五行属什么| 癌胚抗原是什么| 脑白质稀疏什么意思| 杏仁有什么功效| 囹圄是什么意思| 高中什么时候分文理科| 月经提前来是什么原因| 乙肝挂什么科| 肌肉紧张是什么症状| 梦见老公穿新衣服是什么意思| 女人有腰窝意味着什么| 香米是什么米| 风声鹤唳的意思是什么| 脂肪肝吃什么药效果好| 孕妇梦见别人怀孕是什么意思| 阿联酋和迪拜什么关系| 什么是磁场| 冰箱为什么不制冷了| 粿条是什么做的| 九五年属什么| 做腹部彩超挂什么科| 房颤吃什么药效果最好| 阅读是什么意思| 高泌乳素血症是什么原因引起的| 猕猴桃是什么季节的水果| 乳腺囊肿吃什么药| 2021年是属什么年| 626什么意思| 急性肠炎吃什么食物好| 内分泌科主要看什么| 靶向药是什么| 什么淀粉最好| 880什么意思| 总头晕是什么原因| 开飞机是什么意思| 蔓越莓是什么| 爱的本质是什么| 什么海没有鱼| 什么人容易得脑溢血| 六月十五号是什么星座| 白麝香是什么味道| 吃什么去除体内湿热| 什么是满汉全席| 王安石是什么朝代的| 枸杞泡酒有什么作用和功效| 猕猴桃对身体有什么好处| 尿白细胞弱阳性是什么意思| 割包皮是什么意思| 伏藏是什么意思| 眼黄瘤什么方法治疗最好| 发烧了吃什么药| 西乐葆是什么药| 低压偏高有什么危害| 眩晕症吃什么好| 怀孕两周有什么症状| 做梦手机坏了什么预兆| 什么手机拍照效果最好| 丰富多腔的腔是什么意思| 荡是什么意思| anca医学上是什么意思| 右附件区囊肿是什么意思| 什么对眼睛好| 大黄泡水喝有什么功效| 诸葛亮是一个什么样的人| 吃什么睡眠好| 为什么生日不能提前过| fq交友是什么意思| 爱长闭口用什么护肤品| 黄绿色痰液是什么感染| 呼吸困难是什么原因引起的| 什么中华| 坐月子吃什么下奶最快最多最有效| 胃充盈欠佳是什么意思| 梦见自己坐火车是什么意思| 见利忘义是什么意思| 三千年前是什么朝代| 关节痛挂号挂什么科| 甲状腺结节低回声什么意思| 朝鲜和韩国什么关系| 一夫一妻制产生于什么时期| 心房纤颤是什么意思| 内衣为什么会发霉| 胃药吃多了有什么副作用| 酮症酸中毒什么原因引起的| 什么是什么的摇篮| gb10769是什么标准| 右耳朵发热代表什么预兆| 合肥原名叫什么名字| 胎儿没有胎心是什么原因| 小鱼吃什么食物| 劲酒加什么好喝| 一声叹息是什么意思| 祛湿吃什么食物| 心影不大是什么意思| 什么叫次日| 5月25日什么星座| 性早熟有什么危害| 圣经是什么| 甲沟炎去医院挂什么科| 声色什么| 外阴瘙痒什么原因引起| 栉风沐雨是什么意思| 什么闻什么睹| 胆碱能性荨麻疹吃什么药| 为什么会连续两天遗精| 打碎碗是什么预兆| 夏天喝什么饮料好| 中性粒细胞低吃什么药| 脾虚吃什么中成药| 雄五行属什么| hcg值低是什么原因| 什么数码相机好| 三个土是什么字怎么读| 什么茶降火| 12月21日是什么星座| 紫癜是一种什么病严重吗| 血液透析是什么意思| 为什么不可以| 小厮是什么意思| 葛根长什么样子图片| 本虚标实是什么意思| 一库一库雅蠛蝶是什么意思| 什么是固态法白酒| 口幼读什么| 天龙八部是指佛教中的什么| 电解质氯高是什么原因| 长疮是什么原因| 左旋肉碱什么时候吃| 病毒感染发烧吃什么药| 鼻子流水是什么原因| 为什么要吃叶酸| 上位是什么意思| 霸王花是什么花| 什么的东风填词语| 结肠多发憩室是什么意思| 阴蒂痒是什么原因| 穿刺是什么检查| 中国人为什么要学英语| 手肿是什么病的前兆| 上海有什么玩的| 肚子疼用什么药好| save什么意思| 压力山大什么意思| 送情人什么礼物最好| ons是什么| 气管痉挛是什么症状| 什么国家的钱最值钱| 动物园里有什么动物| 外阴瘙痒吃什么药| 去黄疸吃什么药| 荣誉的誉是什么意思| 车加昆念什么| 瑶浴spa是什么意思| 什么是横纹肌溶解症| 水泡型脚气用什么药| 乏是什么单位| prn医学上是什么意思| 吃什么孕酮值可以增高| 偷鸡不成蚀把米是什么意思| rr是什么意思| 小鸟为什么会飞| 黄鼻涕是什么类型的感冒| 低血压高什么原因| 什么气| 两肺纹理增多模糊是什么意思| 溪字五行属什么| 肌酐测定是查什么| 南瓜可以做什么美食| 子宫肌瘤吃什么好| 为什么会说梦话| 耳鸣和脑鸣有什么区别| 不孕为什么要查胰岛素| 乳糖不耐受是什么原因导致的| 进仓是什么意思| 瞩目是什么意思| 水猴子是什么| 1226是什么星座| 梦见好多猫是什么意思| 检查脑袋应该挂什么科| 脾胃不好有什么症状表现| 小肚子鼓鼓的什么原因| 瘦肉炒什么好吃| 梦到死人是什么预兆| 盐酸达泊西汀片是什么药| 请产假需要什么材料| electrolux是什么牌子| 睡觉张嘴是什么原因| 静脉血是什么颜色| 熟啤酒是什么意思| 什么花是绿色的| 夏天什么面料最凉快| 5月13号是什么星座| 故的偏旁是什么| 过奖了是什么意思| 吃茴香有什么好处和坏处| 隙是什么意思| 60岁男人喜欢什么样的女人| dsa检查是什么| 为什么会尿道感染| 男朋友发烧该说些什么| ber是什么意思| 血氨高会导致什么后果| 为什么海藻敷完那么白| 世界最大的岛是什么岛| 益生菌什么时间段吃效果好| pdo是什么意思| 黑枸杞有什么作用| 皮角是什么病| 经期可以吃什么水果| 恭候是什么意思| 新生儿出院回家有什么讲究| 姘头是什么意思| 睡眠时间短是什么原因| 一笑倾城是什么意思| 发物都有什么| 为什么睡觉流口水| 身是什么结构的字| 毛五行属什么| 医学上cr是什么意思| 十万个为什么内容| 扑尔敏是什么药| 黄眉大王是什么妖怪| 泉中水是什么生肖| 乳腺结节是什么原因引起的| 吃秋葵有什么禁忌| 肛塞什么感觉| 百度

为什么说方法的参数最好不要超过4个?

简介

百度 早在20世纪80年代,美国就有医学专家报告说:在80岁上下老年人的尸解中,有1/4的人体内有肿瘤,但这些人生前没有与癌症有关的任何症状。

在很多年前的一次Code Review中,有大佬指出,方法的参数太多了,最好不要超过四个,对于当时还是萌新的我,虽然不知道什么原因,但听人劝,吃饱饭,这个习惯也就传递下来了,直到参加工作很多年后,才明白这其中的缘由。

调用协定

在计算机编程中,调用协定(Calling Convention)是一套关于方法/函数被调用时参数传递方式栈由谁清理寄存器如何使用的规范。

  1. 参数传递方式
  • 寄存器传递:将参数存入CPU寄存器,速度最快。
  • 栈传递:将参数压入调用栈,再依次从栈中取出,速度最慢
  • 混合传递:前N个参数用寄存器,剩余参数用栈,速度适中
  1. 栈由谁清理
  • Caller清理:调用函数后由调用方负责恢复栈指针(如C/C++的__cdecl)。
  • Callee清理:被调用函数返回前自行清理栈(如x64的默认协定)。
  1. 寄存器如何使用
  • 易变寄存器(Volatile Registers):函数调用时可能被修改的寄存器(如x64的RAXRCXRDX),调用方需自行保存这些寄存器的值。
  • 非易变寄存器(Non-Volatile Registers):函数必须保存并恢复的寄存器(如x64的RBXRBPR12-R15)。

x86架构混乱的调用协定

x86架构发展较早,因此调用协定野蛮生长,有多种调用协定

协定名称 参数传递方式 栈清理 适用场景
__cdecl 通过栈传递(右→左) 调用者清理栈 C/C++默认,支持可变参数
__stdcall 通过栈传递(右→左) 被调用者清理栈 Windows API(如Win32)
__fastcall 前两个参数通过寄存器,剩余通过栈(右→左) 被调用者清理栈 高性能场景
__thiscall this指针通过寄存器, 剩余通过栈(右→左) 被调用者清理栈 C++类成员函数

眼见为实

image

image

image

image

可以看到,cdecl,stdcall是通过压栈的方式将参数压入栈中,而fastcall直接赋值给寄存器,并无压栈操作

点击查看代码
#include <iostream>

int __cdecl cdecl_add(int a, int b) {
	return a + b;
}

int __stdcall stdcall_add(int a, int b) {
	return a + b;
}

int __fastcall fastcall_add(int a, int b) {
	return a + b;
}

class Calculator {
public:
	int __thiscall thiscall_add(int b) {
		return this->a + b;
	}
	int a;
};


int main()
{
	int a = 10, b = 5;

	int cdecl_add_value = cdecl_add(a, b);
	int stdcall_add_value = stdcall_add(a, b);
	int fastcall_add_value = fastcall_add(a, b);

	Calculator calc;
	calc.a = 10;

	int thiscall_add_value = calc.thiscall_add(5);
}

x64的大一统

而在x64架构下,为了解决割裂的调用协定,windows与linux实现了统一。

协定名称 参数传递方式 栈清理 适用场景
MS x64 前4个参数通过寄存器,剩余通过栈(左→右) 被调用者清理栈 Windows x64程序
System V AMD64 前6个参数通过寄存器,剩余通过栈(左→右) 被调用者清理栈 Unix/Linux x64程序

眼见为实

image

linux下暂无图(因为我懒),大概就是这意思,自行脑补

点击查看代码
#include <stdio.h>

int add(int a, int b, int c, int d, int e) {
    return a + b + c + d + e;
}

int main() {
    int result = add(1, 2, 3, 4, 5);
    return 0;
}

C#中使用哪种调用协定?

image

C#在x86下,有自己独特的调用协定

协定名称 参数传递方式 栈清理 适用场景
Standard 前两个参数通过寄存器,剩余通过栈(左→右) 被调用者清理栈 C#静态方法
HasThis 前两个参数通过寄存器(第一个为This),剩余通过栈(左→右) 被调用者清理栈 C#实例方法

在x64形成实现统一,与操作系统保持一致

眼见为实

image
image
image

注意寄存器与栈是两片独立运行的区域,光从汇编代码,很容易陷入误区,就拿上图来说,从上往下阅读汇编,你会发现参数传递的顺序是30(1Eh),40(28h),50(32h),10(0Ah),20(14h)。明显不对,这是因为一个是寄存器,一个是线程栈,这是两个不相关的区域,谁前谁后都不违反从左到右的规定。不能死脑筋,寄存器与栈之间是存在位置无关性的。

/*这种顺序也是正确的,寄存器是寄存器,栈是栈,汇编的顺序不影响他们的位置无关性,因为是两片独立运行的区域*/
push 1Eh
mov ecx,0Ah
push 28h
mov edx,14h
push 32h
点击查看代码
    internal class Program
    {
        static void Main(string[] args)
        {
            var t = new Test();
            var sum = t.Add(10, 20, 30, 40, 50);

            var sum2 = Test.StaticAdd(10, 20, 30, 40, 50);

            Console.ReadKey();
        }
    }

    public class Test
    {
        public int Add(int a, int b, int c, int d, int e)
        {
            var sum = a + b + c + d + e;
            return sum;
        }

        public static int StaticAdd(int a, int b, int c, int d, int e)
        {
            var sum = a + b + c + d + e;
            return sum;
        }
    }

结论

可以看到,在Windows x64下,如果方法的参数<=4 那么就就完全避免了栈传递的开销,实现性能最佳化。
image

在linux下,参数为<=6,根据木桶效应,取4为最佳。

当然,此文不是让你严格遵守此规则,随着CPU性能的发展,在微服务集群大行其道的今天。这点性能差距可以忽略不计,权当饭后消遣,补充冷知识,好让你在未来的Code Review中,没活硬整.

点击查看代码
    internal class Program
    {
        static void Main(string[] args)
        {
            ParameterPassingBenchmark.Run();
        }
    }
    public class ParameterPassingBenchmark
    {
        private const int WarmupIterations = 100000;
        private const int BenchmarkIterations = 10000000;
        private const int BatchSize = 1000; // 批量调用次数,提高测量精度
        private static readonly Random _random = new Random(42);

        // x64平台前4个参数通过寄存器传递
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static int Register4Params(int a, int b, int c, int d) => a + b + c + d;

        // 第5个参数通过栈传递
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static int Stack1Param(int a, int b, int c, int d, int e) => a + b + c + d + e;

        // 第5-8个参数通过栈传递
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static int Stack4Params(int a, int b, int c, int d, int e, int f, int g, int h)
            => a + b + c + d + e + f + g + h;

        public static void Run()
        {
            Console.WriteLine($"参数传递性能测试 - 预热: {WarmupIterations:N0}, 测试: {BenchmarkIterations:N0} 次");
            Console.WriteLine("----------------------------------------------------------------");

            // 生成随机输入数据以避免优化
            var inputData = GenerateInputData();

            // 预热
            Warmup(inputData);

            // 测试
            var reg4Time = Measure(() => Register4ParamsTest(inputData));
            var stack1Time = Measure(() => Stack1ParamTest(inputData));
            var stack4Time = Measure(() => Stack4ParamsTest(inputData));

            // 输出结果
            Console.WriteLine("\n===== 测试结果 =====");
            Console.WriteLine($"4寄存器参数: {reg4Time,12:N2} ns/次");
            Console.WriteLine($"4寄存器+1栈参数: {stack1Time,10:N2} ns/次 ({((double)stack1Time / reg4Time - 1) * 100:F1}% 性能下降)");
            Console.WriteLine($"4寄存器+4栈参数: {stack4Time,10:N2} ns/次 ({((double)stack4Time / reg4Time - 1) * 100:F1}% 性能下降)");
        }

        private static (int[], int[], int[]) GenerateInputData()
        {
            var data4 = new int[BenchmarkIterations * 4];
            var data5 = new int[BenchmarkIterations * 5];
            var data8 = new int[BenchmarkIterations * 8];

            for (int i = 0; i < BenchmarkIterations; i++)
            {
                for (int j = 0; j < 4; j++) data4[i * 4 + j] = _random.Next();
                for (int j = 0; j < 5; j++) data5[i * 5 + j] = _random.Next();
                for (int j = 0; j < 8; j++) data8[i * 8 + j] = _random.Next();
            }

            return (data4, data5, data8);
        }

        private static void Warmup((int[], int[], int[]) inputData)
        {
            Console.Write("预热中...");
            var (data4, data5, data8) = inputData;

            for (int i = 0; i < WarmupIterations; i++)
            {
                Register4Params(data4[i * 4], data4[i * 4 + 1], data4[i * 4 + 2], data4[i * 4 + 3]);
                Stack1Param(data5[i * 5], data5[i * 5 + 1], data5[i * 5 + 2], data5[i * 5 + 3], data5[i * 5 + 4]);
                Stack4Params(data8[i * 8], data8[i * 8 + 1], data8[i * 8 + 2], data8[i * 8 + 3],
                            data8[i * 8 + 4], data8[i * 8 + 5], data8[i * 8 + 6], data8[i * 8 + 7]);
            }
            Console.WriteLine("完成");
        }

        private static long Measure(Func<long> testMethod)
        {
            // 强制GC并等待完成
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            // 冷启动
            testMethod();

            // 实际测量
            var stopwatch = Stopwatch.StartNew();
            long result = testMethod();
            stopwatch.Stop();

            // 使用结果以避免被优化掉
            if (result == 0) Console.WriteLine("警告: 结果为0,可能存在优化问题");

            // 计算平均时间(纳秒)
            long totalNs = stopwatch.ElapsedTicks * 10000000L / Stopwatch.Frequency;
            return totalNs / (BenchmarkIterations / BatchSize); // 除以实际调用批次
        }

        private static long Register4ParamsTest((int[], int[], int[]) inputData)
        {
            var (data4, _, _) = inputData;
            long sum = 0;
            int index = 0;

            for (int i = 0; i < BenchmarkIterations / BatchSize; i++)
            {
                // 批量调用以提高测量精度
                for (int j = 0; j < BatchSize; j++)
                {
                    sum += Register4Params(
                        data4[index++],
                        data4[index++],
                        data4[index++],
                        data4[index++]
                    );
                }
            }

            return sum;
        }

        private static long Stack1ParamTest((int[], int[], int[]) inputData)
        {
            var (_, data5, _) = inputData;
            long sum = 0;
            int index = 0;

            for (int i = 0; i < BenchmarkIterations / BatchSize; i++)
            {
                // 批量调用以提高测量精度
                for (int j = 0; j < BatchSize; j++)
                {
                    sum += Stack1Param(
                        data5[index++],
                        data5[index++],
                        data5[index++],
                        data5[index++],
                        data5[index++]
                    );
                }
            }

            return sum;
        }

        private static long Stack4ParamsTest((int[], int[], int[]) inputData)
        {
            var (_, _, data8) = inputData;
            long sum = 0;
            int index = 0;

            for (int i = 0; i < BenchmarkIterations / BatchSize; i++)
            {
                // 批量调用以提高测量精度
                for (int j = 0; j < BatchSize; j++)
                {
                    sum += Stack4Params(
                        data8[index++],
                        data8[index++],
                        data8[index++],
                        data8[index++],
                        data8[index++],
                        data8[index++],
                        data8[index++],
                        data8[index++]
                    );
                }
            }

            return sum;
        }
    }
posted @ 2025-08-05 22:36  叫我安不理  阅读(4273)  评论(33)    收藏  举报
哪里是什么意思 甲五行属什么 dvt是什么意思 关税是什么意思 病人说胡话是什么征兆
5岁属什么 打喷嚏是什么原因 一键挪车什么意思 什么是桥本病 待业是什么意思
妈咪是什么意思 friend什么意思中文 4月18日什么星座 核素治疗是什么 什么食物对眼睛好
电饼铛什么牌子好 茶花什么时候开花 三点水开念什么意思 gxg是什么牌子 再生聚酯纤维是什么面料
血滴子是什么意思dajiketang.com 鸟字旁的字和什么有关hcv7jop9ns2r.cn 蓝色加黄色等于什么颜色hcv9jop0ns8r.cn 复方是什么意思xinjiangjialails.com 蚊子不咬什么体质的人hcv9jop3ns9r.cn
烟草是什么植物clwhiglsz.com 金酒是什么酒hcv9jop1ns5r.cn 食客是什么意思hcv8jop8ns6r.cn 种植什么好imcecn.com 虚岁28岁属什么生肖hcv8jop0ns4r.cn
屁股上的骨头叫什么骨hcv9jop1ns9r.cn 藿香正气水是什么hcv8jop8ns3r.cn 螃蟹为什么横着走hcv8jop6ns7r.cn 牙齿一吸就出血是什么原因hcv8jop4ns4r.cn 血常规wbc是什么意思hcv9jop0ns0r.cn
关节疼痛吃什么药hcv8jop8ns5r.cn 大便黑色是什么原因hcv9jop5ns8r.cn 山药补什么hcv7jop6ns4r.cn 女人下面水多是什么原因hcv7jop9ns0r.cn 经常低血糖是什么原因cj623037.com
百度