Skip to content

Commit

Permalink
修改ChubbyGoMap;测试了多种map的put操作;
Browse files Browse the repository at this point in the history
  • Loading branch information
Super-long committed Oct 31, 2020
1 parent c2705a8 commit 7dab08a
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 41 deletions.
79 changes: 43 additions & 36 deletions BaseServer/chubbyGoMap.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
/**
* Copyright lizhaolong(https://github.com/Super-long)
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* Code comment are all encoded in UTF-8.*/

package BaseServer

import (
"log"
"reflect"
"sync"
)

/*
* @brief: ChubbyGoConcurrentMap写成这样的原因是sync.map和concurrentMap各有优劣,用户可以根据不同的需求来配置
* @notes: 有一个重要的问题就是反射的效率太低,可以在测试中看出ChubbyGo版本的比原版本效率低了不少,内存也消耗了不少
*/

const (
Expand All @@ -15,7 +34,7 @@ const (
)

type ChubbyGoConcurrentMap struct {
MapEntry interface{}
MapEntry reflect.Value
Flag uint32
}

Expand All @@ -24,64 +43,52 @@ func NewChubbyGoMap(flag uint32) *ChubbyGoConcurrentMap{
entry.Flag = flag

if entry.Flag == SyncMap {
entry.MapEntry = sync.Map{}
entry.MapEntry = reflect.ValueOf(&sync.Map{}).Elem()
} else if entry.Flag == ConcurrentMap {
entry.MapEntry = NewConcurrentMap()
// NewConcurrentMap返回的是指针
entry.MapEntry = reflect.ValueOf(NewConcurrentMap()).Elem()
}

return &entry
}

func (hs *ChubbyGoConcurrentMap) ChubbyGoMapGet(key string) (string, bool) {
if hs.Flag == SyncMap {
Map, ok := hs.MapEntry.(sync.Map)
if ok {
res, IsOk := Map.Load(key)
if IsOk {
return res.(string), true
} else {
return "", false
}
Map := hs.MapEntry.Addr().Interface().(*sync.Map)

res, IsOk := Map.Load(key)
if IsOk {
return res.(string), true
} else {
log.Println("INFO : ChubbyGoMapGet predicate failture.")
return "", false
}
} else if hs.Flag == ConcurrentMap {
Map, ok := hs.MapEntry.(ConcurrentHashMap)
if ok {
res, IsOk := Map.Get(key)
if IsOk {
return res.(string), true
} else {
return "", false
}
Map := hs.MapEntry.Addr().Interface().(*ConcurrentHashMap)

res, IsOk := Map.Get(key)
if IsOk {
return res.(string), true
} else {
log.Println("INFO : ChubbyGoMapGet predicate failture.")
return "", false
}
} else {
log.Println("ERROR : ChubbyGoMapSet -> No such situation.")
return "", false
return "",false
}
}

func (hs *ChubbyGoConcurrentMap) ChubbyGoMapSet(key string, value string) {
// sync.map在拷贝以后失效
if hs.Flag == SyncMap {
Map, ok := hs.MapEntry.(sync.Map)
if ok {
Map.Store(key, value)
} else {
log.Println("INFO : ChubbyGoMapSet predicate failture.")
}
hs.MapEntry = Map

Map := hs.MapEntry.Addr().Interface().(*sync.Map)
Map.Store(key, value)

} else if hs.Flag == ConcurrentMap {
Map, ok := hs.MapEntry.(ConcurrentHashMap)
if ok {
Map.Set(key, value)
} else {
log.Println("INFO : ChubbyGoMapSet predicate failture.")
}
hs.MapEntry = Map

Map := hs.MapEntry.Addr().Interface().(*ConcurrentHashMap)
Map.Set(key, value)

} else {
log.Println("ERROR : ChubbyGoMapSet -> No such situation.")
}
Expand Down
46 changes: 43 additions & 3 deletions BaseServer/concurrentHashMap.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

/*
* 这个代码的原型来自于: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Go/go_map_bench_test/concurrent-map/concurrent_map.go
* 我修改了其中的哈希算法
* 我修改了其中的哈希算法;
*/

package BaseServer
Expand Down Expand Up @@ -73,7 +73,7 @@ func NewConcurrentMap() *ConcurrentHashMap {
* @brief: 利用key得到哈希表内的桶
*/
func (m ConcurrentHashMap) GetShard(key string) *ConcurrentMapShared {
return m.ThreadSafeHashMap[uint(xxhash_key(key))%uint(SHARD_COUNT)]
return m.ThreadSafeHashMap[uint(xxhash_key(key))%uint(m.BucketNumber)]
}

func (m ConcurrentHashMap) MSet(data map[string]interface{}) {
Expand Down Expand Up @@ -335,4 +335,44 @@ func xxhash_key(key string) uint32 {
xxh := xxhash.New32()
xxh.Write(str2sbyte(key))
return xxh.Sum32()
}
}

/*
* @brief: 用于与一般ChubbyGoMap作比较
*/
type BaseMap struct {
sync.Mutex
m map[string]interface{}
}

func NewBaseMap() *BaseMap{
return &BaseMap{
m: make(map[string]interface{}, 100),
}
}

func (myMap *BaseMap) BaseStore(k string, v interface{}) {
myMap.Lock()
defer myMap.Unlock()
myMap.m[k] = v
}

func (myMap *BaseMap) BaseGet(k string) interface{} {
myMap.Lock()
defer myMap.Unlock()
if v, ok := myMap.m[k]; !ok {
return -1
} else {
return v
}
}

func (myMap *BaseMap) BaseDelete(k string) {
myMap.Lock()
defer myMap.Unlock()
if _, ok := myMap.m[k]; !ok {
return
} else {
delete(myMap.m, k)
}
}
3 changes: 1 addition & 2 deletions Connect/client_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,7 @@ func (cfg *ClientConfig) Release(fd *BaseServer.FileDescriptor, token uint64) bo

/*
* @param: 给出文件名和手中持有的token,返回**此时刻**token是否有效
* @brief:
* 可以传入绝对路径和相对路径,也不能叫相对路径
* @brief: 可以传入绝对路径和相对路径,也不能叫相对路径
*/
func (cfg *ClientConfig) CheckToken(AbsolutePath string, token uint64) bool {
index := strings.LastIndex(AbsolutePath, "/")
Expand Down
34 changes: 34 additions & 0 deletions MapPerformanceTest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 简介
对于线程安全的hashmap的选取是性能提升的一个重点,摆在面前的有三种选择,BaseMap,sync.map以及concurrentMap。

1. **BaseMap**:map中只有一把锁,所有操作都经过这把锁。
2. **sync.map**:Golang提供的一个线程安全的哈希map,采用无锁实现。
3. **concurrentMap**:每一个哈希桶加一把锁,最大程度并发。

一般情况直接使用标准库提供的就不会有太大问题,但在简单的查阅了sync.map的实现后我认为此种方法在多写的情况下并不高效,因为涉及到很多次的拷贝,为了更优的性能,我测试了这三种方案在不同情况下的性能,最终发现BaseMap效率在任何情况下表现都不突出,而其他两种方法各有优略,最终我选择的版本是客户可以根据不同的应用条件在配置文件中动态的配置选择sync.map还是concurrentMap,为了更优雅的代码与更少的空间消耗,引入反射动态区分这两种类型,最终带来了效率上一定的损耗,但增加了灵活性,我称之为ChubbyGoMap。


以下是BaseMap, sync.map, concurrentMap, 以及ChubbyGoMap的两种类型五种类型分别执行Put,Get,并发插入读取混合,删除四种操作的性能对比,以此为客户提供不同场景下最佳的选择:

测试代码位于/ChubbyGo/MapPerformanceTest/test_test.go:

执行以下命令可以执行测试:

```go
go test -v -run=^$ -bench . -benchmem
```

说明:

1. ns/op : 每次执行平均花费时间;
2. b/op: 每次执行堆上分配内存总数;
3. allocs/op: 每次执行堆上分配内存次数;

## Put
| | ns/op | b/op| allocs/op|
|--|--|--|--|--|
|BaseMap | 2505 | 205| 10
|ConcurrentMap|1257| 239|10
|SyncMap| 4546|500|32
|GoConcurrentMap|1838|423|20
|GoSyncMap|5096|662|42
128 changes: 128 additions & 0 deletions MapPerformanceTest/test_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"ChubbyGo/BaseServer"
"strconv"
"sync"
"testing"
)

/*
* @brief: 这个文件是为了对BaseMap, sync.map, concurrentMap, 以及ChubbyGoMap的两种类型
* 的Put,Get,并发插入读取混合,删除四种操作分别做性能比较, 为客户提供不同场景下最佳的选择.
*/

/*
* 循环次数;每次执行平均花费时间;每次执行堆上分配内存总数;每次执行堆上分配内存次数;
*/

const ThreadNumber = 10

/*1.--------------测试Put操作--------------*/

/*[1]--------------插入不存在的key--------------*/

// 在此测试中冲突键较多,对并发的map并不有利

//(BaseMap)
func BenchmarkSinglePutKeyNoExist_BaseMap(b *testing.B){
Map := BaseServer.NewBaseMap()

groups := sync.WaitGroup{}
groups.Add(ThreadNumber)

b.ResetTimer()

for j:=0; j < ThreadNumber; j++{
go func(){
for i := 0; i < b.N; i++ {
Map.BaseStore(strconv.Itoa(i), "lizhaolong")
}
groups.Done()
}()
}
groups.Wait()
}

//(ConcurrentMap)
func BenchmarkSinglePutKeyNoExist_ConcurrentMap(b *testing.B){
Map := BaseServer.NewConcurrentMap()

groups := sync.WaitGroup{}
groups.Add(ThreadNumber)

b.ResetTimer()

for j:=0; j < ThreadNumber; j++{
go func(){
for i := 0; i < b.N; i++ {
Map.Set(strconv.Itoa(i), "lizhaolong")
}
groups.Done()
}()
}
groups.Wait()
}

// (Sync.map)
func BenchmarkSinglePutKeyNoExist_SyncMap(b *testing.B){
syncMap := &sync.Map{}

groups := sync.WaitGroup{}
groups.Add(ThreadNumber)

b.ResetTimer()

for j:=0; j < ThreadNumber; j++{
go func(){
for i := 0; i < b.N; i++ {
syncMap.Store(strconv.Itoa(i), "lizhaolong")
}
groups.Done()
}()
}
groups.Wait()
}

// (chubbyGoMap.ConcurrentMap)
func BenchmarkSinglePutKeyNoExist_ChubbyGo_ConcurrentMap(b *testing.B){
chubbyGoMap := BaseServer.NewChubbyGoMap(BaseServer.ConcurrentMap)

groups := sync.WaitGroup{}
groups.Add(ThreadNumber)

b.ResetTimer()

for j:=0; j < ThreadNumber; j++{
go func(){
for i := 0; i < b.N; i++ {
chubbyGoMap.ChubbyGoMapSet(strconv.Itoa(i), "lizhaolong")
}
groups.Done()
}()
}
groups.Wait()
}

// (chubbyGoMap.Sync.map)
func BenchmarkSinglePutKeyNoExist_ChubbyGo_SyncMap(b *testing.B){
chubbyGoMap := BaseServer.NewChubbyGoMap(BaseServer.SyncMap)

groups := sync.WaitGroup{}
groups.Add(ThreadNumber)

b.ResetTimer()

for j:=0; j < ThreadNumber; j++{
go func(){
for i := 0; i < b.N; i++ {
chubbyGoMap.ChubbyGoMapSet(strconv.Itoa(i), "lizhaolong")
}
groups.Done()
}()
}
groups.Wait()
}


/*[1]--------------插入存在的key--------------*/

0 comments on commit 7dab08a

Please sign in to comment.