Exclusive Time of Functions 函数的独占时间

本文介绍了一种使用栈的方法来解决非抢占单线程CPU环境下,如何计算多个可能递归调用的函数的独占时间问题。通过解析按时间戳排序的函数运行日志,实现对每个函数独立运行时间的精确计算。

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

给出一个非抢占单线程CPU的 个函数运行日志,找到函数的独占时间。

每个函数都有一个唯一的 Id,从 0 到 n-1,函数可能会递归调用或者被其他函数调用。

日志是具有以下格式的字符串:function_id:start_or_end:timestamp。例如:"0:start:0" 表示函数 0 从 0 时刻开始运行。"0:end:0" 表示函数 0 在 0 时刻结束。

函数的独占时间定义是在该方法中花费的时间,调用其他函数花费的时间不算该函数的独占时间。你需要根据函数的 Id 有序地返回每个函数的独占时间。

示例 1:

输入:
n = 2
logs = 
["0:start:0",
 "1:start:2",
 "1:end:5",
 "0:end:6"]
输出:[3, 4]
说明:
函数 0 在时刻 0 开始,在执行了  2个时间单位结束于时刻 1。
现在函数 0 调用函数 1,函数 1 在时刻 2 开始,执行 4 个时间单位后结束于时刻 5。
函数 0 再次在时刻 6 开始执行,并在时刻 6 结束运行,从而执行了 1 个时间单位。
所以函数 0 总共的执行了 2 +1 =3 个时间单位,函数 1 总共执行了 4 个时间单位。

说明:

  1. 输入的日志会根据时间戳排序,而不是根据日志Id排序。
  2. 你的输出会根据函数Id排序,也就意味着你的输出数组中序号为 0 的元素相当于函数 0 的执行时间。
  3. 两个函数不会在同时开始或结束。
  4. 函数允许被递归调用,直到运行结束。
  5. 1 <= n <= 100

思路:这道题用栈来做,栈里保存的是函数的id,规则如下:我们定义一个之前的时间戳preTime并初始化为0,每次我们遍历到一个新的字符串,就把对应度过的时间(当前时间time-之前的时间preTime)加到结果集res中(res初始化为全0(vector<int> res(n,0)),这里相当于是一段一段的加),更新preTime为time。然后判断类型,如果是start,就压入栈顶,如果是end就再把对应的id的时间加1,preTime也加1。这里对preTime的操作确实让人看着窒息,难以理解。我们换种思路来思考,既然要分解计算出函数的独立运行时间,那么我们就必须在函数之间插入阻断标志,比如题目给的示例:

["0:start:0",

"1:start:2",

"1:end:5",

"0:end:6"]

我们应该更新为如下形式:

["0:start:0",

"0:end:0",

"1:start:2",

"1:end:5",

"0:start:6",

"0:end:6"]

即任意两个函数之间的时间绝对不会有重叠,哪怕函数0是从6开始到6结束也绝对不会和函数1重叠(红色所示),那么我们计算每个函数的时间就很容易了,对于每个闭合区间(函数start-函数end),经过的时间等于(end-start+1)。所以对应到preTime为什么当类型为end时,preTime要加1且res[s.top()]+1,因为当前的字符串对应的操作是end,还属于当前函数的时间范围,所以对应的id的函数间隔要+1且时间preTime要往下移一位。

参考代码:

class Solution {
public:
    vector<int> exclusiveTime(int n, vector<string>& logs) {
	vector<int> res(n, 0);
	stack<int> s;
	int preTime = 0;
	for (string log:logs) {
		int found1 = log.find_first_of(":");
		int found2 = log.find_last_of(":");
		int idx = stoi(log.substr(0,found1));
		string type = log.substr(found1 + 1, found2 - found1 - 1);
		int time = stoi(log.substr(found2+1));
		if (!s.empty()) {
			res[s.top()] += time - preTime;
		}
		preTime = time;
		if (type == "start") s.push(idx);
		else {
			int tmp = s.top(); s.pop();
			++res[tmp];
			preTime++;
		}
	}
	return res;        
    }
};

 

<think>好的,用户现在想了解ARMv8中的独占访问(exclusive)机制。之前他问过可共享内存,现在这个问题也是内存管理的一部分,但更偏向于同步机制。首先,我需要确认用户的需求。他可能是在多核编程或者需要处理并发时遇到了问题,比如如何保证原子操作,避免数据竞争。 用户可能已经知道可共享内存的概念,现在想知道ARMv8如何实现独占访问来确保数据一致性。需要解释清楚LDXR和STXR指令的工作原理,以及它们如何配合使用来实现原子操作。同时,用户可能对独占监视器(Exclusive Monitor)不太了解,需要详细说明其作用,比如跟踪独占访问的状态。 还要注意用户的应用场景,比如是在操作系统开发、驱动编写还是应用层多线程编程。不同的场景可能需要不同的细节。例如,操作系统开发者可能更关心监视器的状态管理和上下文切换的影响,而应用开发者可能更关注如何使用这些指令实现锁或原子变量。 需要对比其他架构,比如x86的CAS指令,帮助用户理解ARMv8的不同之处。此外,用户可能想知道实际使用中的注意事项,比如循环重试、内存屏障的使用,以及缓存一致性对独占访问的影响。还要提到异常处理时监视器状态的失效情况,避免潜在的竞态条件。 最后,确保回答结构清晰,分步骤讲解,可能用代码示例说明LDXR和STXR的使用,强调独占访问在实现锁、无锁数据结构中的重要性。同时,检查是否有遗漏点,比如监视器的硬件实现细节,或者在不同ARMv8处理器中的行为差异,确保信息准确可靠。</think>--- ### ARMv8 独占访问(Exclusive Access)机制详解 在ARMv8架构中,独占访问(Exclusive Access)是实现**原子操作**和**无锁同步**的核心机制,通过`LDXR`(Load Exclusive Register)和`STXR`(Store Exclusive Register)指令实现。以下是其工作原理与应用场景的逐步解析: --- ### 一、独占访问的核心目标 - **原子性**:确保对内存的“读-修改-写”操作在多核/多线程环境中不可分割。 - **无锁同步**:避免传统锁(如自旋锁)的开销,提升并发性能。 --- ### 二、关键组件:独占监视器(Exclusive Monitor) ARMv8为每个核维护一个**独占监视器**,跟踪独占访问状态: 1. **本地独占监视器**(Local Monitor) - 每个核独立维护,标记当前核的独占访问状态。 2. **全局独占监视器**(Global Monitor) - 针对特定内存地址的全局状态(仅在多核系统中存在)。 --- ### 三、独占访问指令流程 #### 1. **加载独占(LDXR)** - **操作**:从内存地址读取值到寄存器,并标记该地址为“独占访问状态”。 - **示例**: ```assembly LDXR X0, [X1] // 从X1指向的地址加载值到X0,并激活独占监视器 ``` #### 2. **修改数据** - 在本地寄存器中执行计算(如递增、位操作)。 ```assembly ADD X0, X0, #1 // 对X0的值加1 ``` #### 3. **存储独占(STXR)** - **操作**:尝试将寄存器值写回内存地址。 - 若监视器状态有效(期间无其他核修改该地址),则写入成功,返回`0`。 - 若监视器失效(其他核已修改地址),则写入失败,返回`1`。 - **示例**: ```assembly STXR W2, X0, [X1] // 尝试将X0写入[X1],结果状态存入W2 ``` #### 4. **重试机制** - 若`STXR`失败(W2≠0),需重新执行`LDXR→修改→STXR`流程。 ```assembly retry_loop: LDXR X0, [X1] ADD X0, X0, #1 STXR W2, X0, [X1] CBNZ W2, retry_loop // 若失败则重试 ``` --- ### 四、独占访问的特性 1. **地址粒度** - 独占监视器以**自然对齐的地址块**(通常为缓存行大小,如64字节)为单位跟踪状态。 - 若同一块内任一地址被修改,监视器均会失效。 2. **上下文切换影响** - 进程切换或异常(如中断)会导致独占监视器状态**被清除**,需重新尝试。 3. **多核一致性** - 全局独占监视器通过缓存一致性协议(如MESI)维护多核间的状态同步。 --- ### 五、应用场景 #### 1. 原子变量操作 - 实现原子计数器、标志位等: ```c // C内联汇编实现原子递增 void atomic_inc(uint64_t *addr) { uint32_t status; do { asm volatile("ldxr x0, [%1]\n" "add x0, x0, #1\n" "stxr %w0, x0, [%1]" : "=r"(status) : "r"(addr) : "x0", "memory"); } while (status != 0); } ``` #### 2. 自旋锁(Spinlock) - 基于独占访问实现轻量级锁: ```assembly acquire_lock: LDXR X0, [X1] // 加载锁状态 CBNZ X0, acquire_lock // 若锁被占用则重试 MOV X0, #1 STXR W2, X0, [X1] // 尝试获取锁 CBNZ W2, acquire_lock RET release_lock: STLR XZR, [X1] // 使用Store-Release指令释放锁 RET ``` #### 3. 无锁数据结构 - 实现无锁队列、栈等,避免锁争用。 --- ### 六、与x86架构对比 | 特性 | ARMv8 (LDXR/STXR) | x86 (CMPXCHG) | |---------------------|-----------------------------|----------------------------| | **实现方式** | 独占监视器 + 重试循环 | 锁总线或缓存锁 | | **粒度** | 地址块(如缓存行) | 单个内存地址 | | **性能优化** | 冲突少时效率高 | 高竞争时可能更优 | | **指令扩展** | LDAXR/STLXR(Acquire/Release语义) | LOCK前缀指令 | --- ### 七、关键注意事项 1. **内存屏障** - 使用`LDAR`(Load-Acquire)和`STLR`(Store-Release)保证内存顺序。 - 示例:释放锁时需确保之前的写操作对其他核可见。 ```assembly STLR X0, [X1] // Store-Release语义 ``` 2. **缓存对齐** - 独占访问地址应缓存对齐,避免伪共享(False Sharing)。 3. **短临界区** - 独占访问适合轻量级操作,长时间占用会导致重试开销增加。 --- ### 八、总结 ARMv8的独占访问机制通过硬件级监视器和`LDXR/STXR`指令,为多核环境提供了高效的原子操作支持。其核心优势在于: - **低开销**:无需锁总线,减少争用。 - **灵活性**:适用于自旋锁、无锁算法等多种场景。 - **可扩展性**:结合内存屏障指令,支持复杂的内存顺序模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值