建议28:理解延迟求值和主动求值之间的区别

本文通过实例对比了延迟求值与主动求值的区别,重点介绍了在使用LINQ时,延迟求值如何帮助提高性能,特别是在数据库查询场景下。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

建议28:理解延迟求值和主动求值之间的区别

要理解延迟求值(lazy evaluation)和主动求值(eager evaluation),先看个例子:

复制代码
            List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            var temp1 = from c in list where c > 5 select c;
            var temp2 = (from c in list where c > 5 select c).ToList<int>();
            list[0] = 11;
            Console.Write("temp1: ");
            foreach (var item in temp1)
            {
                Console.Write(item + " ");
            }
            Console.Write("\ntemp2: ");
            foreach (var item in temp2)
            {
                Console.Write(item + " ");
            }
复制代码

输出:

temp1: 11 6 7 8 9
temp2: 6 7 8 9

在延迟求职的情况下,只是定义了一个查询,而不是立刻执行。对查询结果的访问每次都会遍历原集合。如上文中对temp1的迭代,在迭代前,我们修改了list[0]的值,可见,修改直接影响了迭代的输出。对查询调用ToList、ToArray等方法,将会使其立即执行,由于对于list[0]的修改是在temp2查询之后进行的,所以针对list[0]的修改不会影响到temp2的结果。

在使用LINQ to SQL 时,延迟求值能带来显著的性能提升。例如:若果定义了两个查询,而且采用延迟求值,CLR则会合并两次查询并生成一个最终的查询:

复制代码
        static void Main(string[] args)
        {
            DataContext ctx = new DataContext("server=192.168.0.102;database=Temp;uid=sa;pwd=sa123");
            Table<Person> persons = ctx.GetTable<Person>();

            var temp1 = from p in persons where p.Age > 20 select p;
            //省略
            var temp2 = from p in temp1 where p.Name.IndexOf('e') > 0 select p;
            foreach (var item in temp2)
            {
                Console.WriteLine(string.Format("Name:{0}\tAge:{1}", item.Name, item.Age));
            }

        }

        [Table(Name = "Person")]
        class Person
        {
            [Column]
            public string Name { get; set; }
            [Column]
            public int Age { get; set; }
        }
复制代码

注意:这段代码需要SQL Server数据库的支持。本段代码假设已经存在一个Temp的数据库,其中有一个Person表,表内含有两个字段:

Name , varchar(50)

Age , int

迭代开始的时候,LINQ to SQL 引擎会生成如下SQL查询语句:

exec sp_executesql N'SELECT [t0].[Name], [t0].[Age]

FROM [Person] AS [t0]

WHERE ((

  (CASE

    WHEN (DATALENGTH(@p0) / 2) = 0 THEN CONVERT(BigInt,0)

    ELSE CONVERT(BigInt,(CONVERT(Int,CHARINDEX(@p0, [t0].[Name])))-1)

  END)) > @p1) AND ([t0].[Age]>@p2)',N'@p0 nchar(1),@p1 int,@p2 int',@p0=N'e',@p1=0,@p2=20

 

最终的SQL语句合并了对年龄和姓名条件的查询。

如果Person表中的值如下:

根据上面查询将返回:

Name:Steve  Age:21

Name:Jessica  Age:22

如果采用主动求值:

            var temp1 = (from p in persons where p.Age > 20 select p).ToList<Person>();
            //省略
            var temp2 = from p in temp1 where p.Name.IndexOf('e') > 0 select p;

会生成下面的语句:

exec sp_executesql N'SELECT [t0].[Name], [to].[Age]

FROM [Person] AS [t0]

WHERE [t0].[Age]>@p0',N'@p0 int',@p0=20

数据库会返回3条数据

Name:Steve  Age:21

Name:Jessica  Age:22

Name:Lisa  Age:23

虽然temp2的查询返回的结果也是两条记录,但是针对temp2的查询实际是对已经返回到本地的3条数据进行的筛选。这个例子中,返回3条或2条带来的效率问题并不明显,但是,将应用放到互联网系统中,每个地方减少一定的流量,则会给我们带来可观的性能提升。

事实上,应该仔细体会延迟求值和主动求值之间的区别,体会两者在应用中会带来什么样的输出结果:否则,很有肯能会出现一些我们意想不到的Bug。

 

 

转自:《编写高质量代码改善C#程序的157个建议》陆敏技

<think>嗯,用户之前问过关于ILOGICE3OLOGICE3如何帮助对齐时钟边沿的问题,现在他们似乎对GMII转RGMII的时序要求有了新的理解,认为可能不需要严格遵守RGMII的时序规定。用户提到数据控制信号没有严格对齐,只要时钟在PHY被延迟就能采集数据。这需要仔细分析是否正确。 首先,我需要回想RGMIIGMII接口的区别。RGMII使用双沿采样,数据速率是GMII的两倍,因此时序要求更严格。用户可能认为只要PHY处理了时钟延迟,就可以忽略严格对齐,这可能存在误区。需要确认RGMII规范中的具体时序参数,比如时钟与数据之间延迟要求。 接下来,用户提到的PHY延迟时钟,可能指的是PHY芯片内部有时钟数据恢复(CDR)或延迟调整功能。但即使PHY有这些功能,发送端仍然需要满足建立保持时间,否则无法保证数据正确采样。例如,RGMII规范通常要求数据在时钟边沿前后有一定的稳定时间。 然后,用户的问题可能来源于他们自己的项目经验,可能在调试中发现即使数据控制信号没有严格对齐,PHY仍然能工作,但这可能是在特定条件下的情况,比如低速或短距离传输。需要指出这种做法的风险,比如在高速或长距离时可能出现问题。 另外,用户可能误解了时钟延迟的作用。PHY内部的延迟调整确实可以补偿一定的时序偏差,但这通常是在设计允许的范围内。如果发送端完全不遵守时序要求,超过PHY的调整能力,仍然会导致采样错误。 还需要考虑FPGA实现时的约束,比如使用OLOGICE3/ILOGICE3模块时,必须正确设置输入输出延迟约束,否则Vivado无法正确优化布局布线。用户可能没有正确设置约束,导致实际硬件中出现时序问题。 可能用户的实际问题是在转换GMII到RGMII时,没有正确处理数据控制信号的相位,例如TX_CTL信号在RGMII中需要与时钟同步,并且在上升沿下降沿分别表示不同的控制信息。如果这些信号没有正确对齐,PHY可能无法正确解析数据包,导致传输错误。 最后,需要总结用户的误区,明确RGMII时序的重要性,即使PHY有补偿机制,发送端仍需满足基本时序要求,否则会导致可靠性问题。同时,提供正确的实现方法,如使用IDDR/ODDRIODELAY模块,并设置适当的约束。</think>您的理解 **存在误区**,RGMII接口的时序要求必须严格遵守,否则可能导致PHY无法正确采样数据。以下是详细分析: --- ### **一、RGMII接口的时序要求** #### 1. **RGMII协议的核心规则** - **时钟与数据关系**: - RGMII的发送端(FPGA到PHY)需满足 **时钟边沿与数据/控制信号的严格对齐**。 - **TX_CLK** 的上升沿下降沿分别对应 **TXD[3:0]TX_CTL** 的切换点(典型要求:时钟边沿±0.5ns内数据需稳定)。 - **接收端(PHY到FPGA)**: - PHY输出的RX_CLK会主动补偿PCB延迟(通过内部Delay Line),但FPGA仍需通过IODELAY调整数据相位以满足IDDR采样需求。 #### 2. **为何不能忽略时序对齐?** - **PHY的采样机制**:即使PHY内部有延迟调整能力,其采样窗口仍有限(通常仅1-2ns)。若数据与时钟边沿偏差过大,会导致 **建立/保持时间违例**。 - **控制信号的特殊性**:RGMII的TX_CTL信号在上升沿表示数据有效(TXD[3:0]有效),下降沿表示错误指示。若未对齐,PHY可能误判数据帧边界。 --- ### **二、GMII转RGMII的关键设计要点** #### 1. **发送路径(FPGA → PHY)** - **数据速率转换**: - GMII为8位@125MHz,RGMII为4位@125MHz DDR(等效250Mbps)。 - 需使用OLOGICE3的ODDR模块,将8位数据拆分为4位双沿输出。 - **时序对齐要求**: ```verilog ODDR #( .DDR_CLK_EDGE("SAME_EDGE") // 确保TXDTX_CTL在时钟边沿对齐 ) oddr_txdata ( .Q(TXD[i]), .C(tx_clk), .D1(gmii_txd_high[i]), // 上升沿数据(高位半字节) .D2(gmii_txd_low[i]) // 下降沿数据(低位半字节) ); ``` - **TX_CTL信号处理**: - TX_CTL需在时钟上升沿表示`gmii_tx_en`(数据有效),下降沿表示`gmii_tx_err`(错误标志)。 - 若未对齐,PHY可能将错误标志误认为数据有效信号。 #### 2. **接收路径(PHY → FPGA)** - **PHY的时钟补偿**: - PHY输出的RX_CLK已通过内部Delay Line对齐数据,但FPGA仍需使用ILOGICE3的IDDRIODELAY模块进一步微调。 - **FPGA侧校准**: ```verilog IDELAYCTRL idelayctrl_inst (.REFCLK(ref_clk_200MHz)); // 校准IODELAY抽头精度 IDELAYE2 #( .DELAY_SRC("IDATAIN") ) iodelay_rxd ( .IDATAIN(RXD[i]), .DATAOUT(rxd_delayed[i]), .CNTVALUEIN(tap_value) // 动态调整抽头值以对齐RX_CLK ); ``` --- ### **三、常见误区与风险** #### 1. **误区:“PHY内部延迟可完全补偿时序偏差”** - **实际情况**: - PHY的Delay Line仅能补偿固定范围的延迟(通常±1ns)。 - 若FPGA发送的TXD/TX_CTL与TX_CLK偏差超过PHY容忍范围,仍会导致采样错误。 #### 2. **风险案例** - **未对齐的TX_CTL信号**: - 假设TX_CTL在时钟上升沿未稳定,PHY可能在时钟边沿采样到亚稳态,误判数据帧起始。 - **数据偏移过大**: - 若TXD信号相对TX_CLK延迟超过1ns,在高温/低压等工况下,PHY采样窗口可能无法覆盖数据稳定区。 --- ### **四、正确实现GMII转RGMII的建议** #### 1. **发送端设计** - **使用OLOGICE3的ODDR**: - 将GMII的8位数据拆分为4位双沿输出。 - 严格约束TX_CLK与TXD/TX_CTL的相位关系。 - **约束示例(Vivado XDC)**: ```tcl set_output_delay -clock [get_clocks tx_clk] -max 0.5 [get_ports {TXD* TX_CTL}] set_output_delay -clock [get_clocks tx_clk] -min -0.5 [get_ports {TXD* TX_CTL}] ``` #### 2. **接收端设计** - **动态校准IODELAY**: - 通过训练序列(如连续0x55/0xAA)检测数据眼图,动态调整IODELAY抽头值。 - **代码片段**: ```verilog always @(posedge rx_clk) begin if (training_enable) begin if (rxd_delayed != expected_data) tap_value <= tap_value + 1; // 逐步调整抽头值直至数据匹配 end end ``` --- ### **五、总结** - **RGMII时序必须严格遵守**:PHY的延迟补偿能力有限,发送端需确保数据/控制信号在时钟边沿稳定。 - **FPGA侧的核心任务**: 1. 使用ODDR/IDDRIODELAY实现数据速率转换与相位对齐。 2. 通过时序约束指导Vivado优化关键路径。 - **忽略时序的风险**:轻则导致间歇性丢包,重则使PHY完全无法识别数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值