Проблемы с собственным решением @ wsanville, частично упомянутое ранее:
- другие части вашей базы кода могут блокировать одни и те же экземпляры интернированных строк для различных целей , вызывая только производительностьпроблемы, если повезет, и взаимоблокировки, если не повезло (возможно, только в будущем, по мере роста базы кода, расширяемой кодировщиками, не подозревающими о вашем
String.Intern
шаблоне блокировки) - обратите внимание, что это включает в себя блокировки для той же самой интернированной строки даже если они находятся в разных доменах приложений , что может привести к взаимным блокировкам между доменами приложений - невозможно для вас восстановить внутреннюю память в случае, если вы решили это сделатьтак что
String.Intern()
медленно
Чтобы решить все эти 3 проблемы, вы можете реализовать свои собственные Intern()
, которые вы привязываете к своемуконкретное назначение блокировки , т.е. не использовать его как глобальный универсальный string string :
private static readonly ConcurrentDictionary<string, string> concSafe =
new ConcurrentDictionary<string, string>();
static string InternConcurrentSafe(string s)
{
return concSafe.GetOrAdd(s, String.Copy);
}
Я назвал этот метод ...Safe()
, потому что когда яя не буду хранить переданный в String
экземпляр, так как это может быть, например, уже интернированный String
, что делает его подверженным проблемам, упомянутым в 1. выше.
Для сравнения производительности различных способовиз интернирующих строк, я также попробовал следующие 2 метода, а также String.Intern
.
private static readonly ConcurrentDictionary<string, string> conc =
new ConcurrentDictionary<string, string>();
static string InternConcurrent(string s)
{
return conc.GetOrAdd(s, s);
}
private static readonly Dictionary<string, string> locked =
new Dictionary<string, string>(5000);
static string InternLocked(string s)
{
string interned;
lock (locked)
if (!locked.TryGetValue(s, out interned))
interned = locked[s] = s;
return interned;
}
Benchmark
100 потоков, каждый из которых выбирался случайным образом50000 раз одну из 5000 разных строк (каждая из которых содержит 8 цифр), а затем вызывать соответствующий метод intern.Все значения после прогрева достаточно.Это Windows 7, 64-битная, на 4core i5.
NB. Прогрев вышеописанной настройки подразумевает, что после прогрева не будет никаких записей в соответствующие международные словари, нотолько читает .Это то, что меня интересовало для рассматриваемого варианта использования, но различные соотношения записи / чтения, вероятно, повлияют на результаты.
Результаты
String.Intern
(): 2032 мс InternLocked()
: 1245 мс InternConcurrent()
: 458 мс InternConcurrentSafe()
: 453 мс
Тот факт, что InternConcurrentSafe
так же быстр, как и InternConcurrent
, имеет смысл в свете того факта, что эти цифры после прогрева (см. Выше NB), поэтому на самом деле нет или только несколько вызовов String.Copy
во времяtest.
Чтобы правильно инкапсулировать это, создайте класс, подобный следующему:
public class StringLocker
{
private readonly ConcurrentDictionary<string, string> _locks =
new ConcurrentDictionary<string, string>();
public string GetLockObject(string s)
{
return _locks.GetOrAdd(s, String.Copy);
}
}
и после создания одного StringLocker
для каждого варианта использования, который у вас может быть, этотак же просто, как позвонить
lock(myStringLocker.GetLockObject(s))
{
...
NB
Подумав еще раз, нет необходимости возвращать объект типа string
если все, что вы хотите сделать, это заблокировать его, то копирование символов совершенно не нужно, и следующее будет работать лучше, чем в предыдущем классе.
public class StringLocker
{
private readonly ConcurrentDictionary<string, object> _locks =
new ConcurrentDictionary<string, object>();
public object GetLockObject(string s)
{
return _locks.GetOrAdd(s, k => new object());
}
}