我知道,全局变量“不性感”,但我目前的项目中很少。我玩过Xcode的Thread Sanitizer并发现了数据竞争。所以我试着让它们变得安全。
因为我还想要对这个变量进行单点管理。我试图在getter和变量的setter中做GCD的东西。
最后我找到了一个有效的解决方案,被编译器接受并且Thread Sanitizer很高兴......但是......这个解决方案看起来很丑陋(见下文)并且非常慢(做了性能测试并且它很慢)。
是的,我知道,如果我为此使用类,它可能更“swifty”,但是对于线程安全的全局变量必须有一个简单的解决方案。
那么你会如此善良并给出提示和建议来优化这种尝试吗?欢迎Anyt提示/想法/建议/评论!
j
答案 0 :(得分:1)
OOPer让我重做性能测试,因为结果很奇怪。
嗯,他是对的。我写了一个简单的应用程序(Performance Test App on GitHub)并附上了一些截图。
我在带有最新IOS的iPhone SE上运行测试。该应用程序是在设备上启动的,而不是在Xcode中启动的。所有显示的测试结果的编译器设置都是“debug”。我也测试了“完全优化”(最小的最快[-Os]),但结果非常相似。我认为在简单的测试中并没有太多优化。
测试应用程序只运行上面答案中描述的测试。为了使它更加真实,它正在使用qos类.userInteractive,.default和.background对三个并行的异步调度队列进行每个测试。
可能有更好的方法来测试这些东西。但就这个问题而言,我认为这已经足够了。
如果有人重新评估代码并且可能找到更好的测试算法,我很高兴......我们都会从中学习。我现在停止了我的工作。
我的眼睛结果很奇怪。所有三种不同的方法都提供了大致相同的性能每次跑步都有另一个“英雄”,所以我认为它只受其他背景任务等的影响。所以即使是Itai Ferber“漂亮”的解决方案在实践中也没有任何好处。它只是一个更优雅的代码。
是的,线程保存解决方案比未排队的解决方案慢。
这是主要的学习:是的,可以使全局变量线程安全,但是它存在严重的性能问题。
答案 1 :(得分:0)
编辑:我留下第一个答案来保留历史记录,但是一些OOPer的暗示导致了完全不同的观点(见下一个答案)。
首先:我对答案流入的速度和受过良好教育印象非常深刻(我们是在周末!)
所以Itai Ferber的建议非常好,正如他所说,我做了一些性能测试,只是为了给他一些回报; - )
我在操场上用附加的代码运行测试。正如您所看到的,这是迄今为止不是一个设计良好的性能测试,它只是一个简单的测试,以获得性能影响的要点。我做了几次迭代(见下表)。
再说一次:我是在游乐场做的,所以绝对的时间会更好一些真实的"应用程序,但测试之间的差异将非常相似。
主要发现:
互动显示线性行为(如预期的那样)
"我"解决方案(test1)比"未排队"慢约15倍。全局变量(test0)
我做了一个测试,我使用了一个额外的全局变量作为辅助变量(test2),这稍微快一些,但不是真正的突破
来自Itai Ferber(test3)的建议解决方案比纯全局变量(test0)慢大约6到7倍......所以它的速度是" my"解决方案
所以替代3不仅看起来更好,因为它不需要辅助变量的开销也更快。
// the queue to synchronze data access, it's a concurrent one
fileprivate let globalDataQueue = DispatchQueue(
label: "com.ACME.globalDataQueue",
attributes: .concurrent)
// ------------------------------------------------------------------------------------------------
// Base Version: Just a global variable
// this is the global "variable" we worked with
var globalVariable : Int = 0
// ------------------------------------------------------------------------------------------------
// Alternative 1: with concurrent queue, helper variable insider getter
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable1_Value : Int = 0
// this is the global "variable" we worked with
var globalVariable1 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable1_Value = newValue
}
}
get {
// we need a helper variable to store the result.
// inside a void closure you are not allow to "return"
var globalVariable1_Helper : Int = 0
globalDataQueue.sync{
globalVariable1_Helper = globalVariable1_Value
}
return globalVariable1_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 2: with concurrent queue, helper variable as additional global variable
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable2_Value : Int = 0
var globalVariable2_Helper : Int = 0
// this is the global "variable" we worked with
var globalVariable2 : Int {
// the setter
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable2_Value = newValue
}
}
// the getter
get {
globalDataQueue.sync{
globalVariable2_Helper = globalVariable2_Value
}
return globalVariable2_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 3: with concurrent queue, no helper variable as Itai Ferber suggested
// "compact" design
var globalVariable3_Value : Int = 0
var globalVariable3 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) { globalVariable3_Value = newValue }
}
get {
return globalDataQueue.sync { globalVariable3_Value }
}
}
// ------------------------------------------------------------------------------------------------
// -- Testing
// variable for read test
var testVar = 0
let numberOfInterations = 2
// Test 0
print ("\nStart test0: simple global variable, not thread safe")
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable
globalVariable += 1
}
let endTime = CFAbsoluteTimeGetCurrent()
let timeDiff = endTime - startTime
print("globalVariable == \(globalVariable), test0 time needed \(timeDiff) seconds")
// Test 1
testVar = 0
print ("\nStart test1: concurrent queue, helper variable inside getter")
let startTime1 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable1
globalVariable1 += 1
}
let endTime1 = CFAbsoluteTimeGetCurrent()
let timeDiff1 = endTime1 - startTime1
print("globalVariable == \(globalVariable1), test1 time needed \(timeDiff1) seconds")
// Test 2
testVar = 0
print ("\nStart test2: with concurrent queue, helper variable as an additional global variable")
let startTime2 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable2
globalVariable2 += 1
}
let endTime2 = CFAbsoluteTimeGetCurrent()
let timeDiff2 = endTime2 - startTime2
print("globalVariable == \(globalVariable2), test2 time needed \(timeDiff2) seconds")
// Test 3
testVar = 0
print ("\nStart test3: with concurrent queue, no helper variable as Itai Ferber suggested")
let startTime3 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable3
globalVariable3 += 1
}
let endTime3 = CFAbsoluteTimeGetCurrent()
let timeDiff3 = endTime3 - startTime3
print("globalVariable == \(globalVariable3), test3 time needed \(timeDiff3) seconds")