LINUX.ORG.RU

Программа на Go потребляет очень много оперативной памяти.

 


0

2

Добрый день. Написал программу, которая тупо открывает csv файлы и читает их. Размер файлов около 100 МБ. Файлы полностью считываются, сортируются и закрываются. Проблема в том, что программа в какой-то момент заняла около 1200 МБ, после этого я остановил работу программы.

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"os"
	"path/filepath"
	"log"
	"sort"
)

type FInfo struct {
	path string
	FileInfo os.FileInfo
}

type FInfoSlice []FInfo

func (slice FInfoSlice) Len() int {
	return len(slice)
}

func (slice FInfoSlice) Less(i, j int) bool {
	return slice[j].FileInfo.ModTime().Before(slice[i].FileInfo.ModTime())
}
func (slice FInfoSlice) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

func getListFiles(paths []string) []string {
	count_paths := len(paths)
	info := make(FInfoSlice, count_paths)
	for index, path := range paths {
		info[index].FileInfo, _ = os.Stat(path)
		info[index].path = path
	}
	sort.Sort(info)
	new_paths := paths
	for index, f_struct := range info {
		new_paths[index] = f_struct.path
	}
	return new_paths
}

type csvSlice [][]string

func (c csvSlice) Len() int {
	return len(c)
}
func (c csvSlice) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}
func (c csvSlice) Less(i, j int) bool {
	return c[i][4] < c[j][4]
}

func readCsv(path string) [][]string {
	file, err := os.Open(path)
	defer file.Close()
	if err != nil {
		log.Fatal(err)
	}
	reader := csv.NewReader(bufio.NewReader(file))
	reader.Comma = ';'
	values, _ := reader.ReadAll()
	sort_values := values[1:]
	sort.Sort(csvSlice(sort_values))
	return sort_values
}

func in(name string, info map[string]*csv.Writer) bool {
	for i_name, _ := range info {
		if i_name == name {
			return true
		}
	}
	return false
}

func writeCsvFile(path string) {
	for range readCsv(path) {
		continue
	}
}

func main() {
	pattern_out := fmt.Sprintf("%s/*.csv", os.Args[2])
	old_files, _ := filepath.Glob(pattern_out)
	for _, path := range old_files {
		os.Remove(path)
	}

	pattern_in := fmt.Sprintf("%s/*.csv", os.Args[1])
	paths, err := filepath.Glob(pattern_in)
	if err != nil {
		log.Fatal(err)
	}
	f_info := getListFiles(paths)
	for _, path := range f_info {
		writeCsvFile(path)
	}
}

Есть подозрение, что как-то не так работает уборка мусора или я что-то делаю не так.

go version go1.6 linux/386

★★
Ответ на: комментарий от dicos

Можно попробовать вызывать runtime.GC() сразу после обработки файла: https://golang.org/src/runtime/mgc.go?s=33002:33011#L829

        for _, path := range f_info {
                writeCsvFile(path)
                runtime.GC() /// <- здесь
        }

После этого использование heap у меня больше 700MB не поднимается.

sf ★★★
()
Ответ на: комментарий от sf

Не работает, ошибку выдает:

fatal error: runtime: cannot allocate memory

runtime stack:
runtime.throw(0x8141ee0, 0x1f)
	/home/dmitry/go/src/runtime/panic.go:530 +0x7f
runtime.persistentalloc1(0x800, 0x40, 0x81c0718, 0xbffe2800)
	/home/dmitry/go/src/runtime/malloc.go:937 +0x256
runtime.persistentalloc.func1()
	/home/dmitry/go/src/runtime/malloc.go:890 +0x35
runtime.systemstack(0x18435ec8)
	/home/dmitry/go/src/runtime/asm_386.s:329 +0x88
runtime.persistentalloc(0x800, 0x40, 0x81c0718, 0x66)
	/home/dmitry/go/src/runtime/malloc.go:891 +0x4e
runtime.getempty(0x65, 0xb761a000)
	/home/dmitry/go/src/runtime/mgcwork.go:347 +0x87
runtime.(*gcWork).init(0x1841c928)
	/home/dmitry/go/src/runtime/mgcwork.go:97 +0x21
runtime.(*gcWork).put(0x1841c928, 0x2a281b40)
	/home/dmitry/go/src/runtime/mgcwork.go:113 +0x34
runtime.greyobject(0x2a281b40, 0x0, 0x0, 0x17217e4b, 0x0, 0x7c4865a8, 0x1841c928)
	/home/dmitry/go/src/runtime/mgcmark.go:1090 +0x295
runtime.shade(0x2a281b40)
	/home/dmitry/go/src/runtime/mgcmark.go:1028 +0x9b
runtime.gcmarkwb_m(0x19cc68a4, 0x2a281b40)
	/home/dmitry/go/src/runtime/mbarrier.go:94 +0xa8
runtime.writebarrierptr_nostore1.func1()
	/home/dmitry/go/src/runtime/mbarrier.go:120 +0x110
runtime.systemstack(0x1841c000)
	/home/dmitry/go/src/runtime/asm_386.s:313 +0x5e
runtime.mstart()
	/home/dmitry/go/src/runtime/proc.go:1048

goroutine 1 [running]:
runtime.systemstack_switch()
	/home/dmitry/go/src/runtime/asm_386.s:267 fp=0x1852ab70 sp=0x1852ab6c
runtime.writebarrierptr_nostore1(0x19cc68a4, 0x2a281b40)
	/home/dmitry/go/src/runtime/mbarrier.go:121 +0x5c fp=0x1852ab8c sp=0x1852ab70
runtime.writebarrierptr_nostore(0x19cc68a4, 0x2a281b40)
	/home/dmitry/go/src/runtime/mbarrier.go:159 +0x5a fp=0x1852ab98 sp=0x1852ab8c
runtime.heapBitsBulkBarrier(0x19cc68a4, 0xc)
	/home/dmitry/go/src/runtime/mbitmap.go:437 +0x1ac fp=0x1852abe4 sp=0x1852ab98
runtime.typedmemmove(0x80f9d40, 0x19cc68a4, 0x2b1108a4)
	/home/dmitry/go/src/runtime/mbarrier.go:197 +0x82 fp=0x1852abfc sp=0x1852abe4
runtime.growslice(0x80f9300, 0x2a39a000, 0x169800, 0x169800, 0x169801, 0x0, 0x0, 0x0)
	/home/dmitry/go/src/runtime/slice.go:105 +0x2b9 fp=0x1852ac3c sp=0x1852abfc
encoding/csv.(*Reader).ReadAll(0x18418180, 0x2a39a000, 0x169800, 0x169800, 0x0, 0x0)
	/home/dmitry/go/src/encoding/csv/reader.go:170 +0x145 fp=0x1852ac74 sp=0x1852ac3c
main.readCsv(0x1854d410, 0x30, 0x0, 0x0, 0x0)
	/home/dmitry/web/moex_info_/moex_info/catalog.go:66 +0x1fa fp=0x1852ad5c sp=0x1852ac74
main.writeCsvFile(0x1854d410, 0x30)
	/home/dmitry/web/moex_info_/moex_info/catalog.go:79 +0xe9 fp=0x1852aef8 sp=0x1852ad5c
main.main()
	/home/dmitry/web/moex_info_/moex_info/catalog.go:115 +0x3c5 fp=0x1852afb0 sp=0x1852aef8
runtime.main()
	/home/dmitry/go/src/runtime/proc.go:188 +0x276 fp=0x1852afd8 sp=0x1852afb0
runtime.goexit()
	/home/dmitry/go/src/runtime/asm_386.s:1585 +0x1 fp=0x1852afdc sp=0x1852afd8

real	142m49.837s
user	122m18.900s
sys	4m24.064s
Оперативной памяти на компьютере 4 ГБ.

dicos ★★
() автор топика
Ответ на: комментарий от dicos

Похоже на https://github.com/golang/go/issues/12233

TL;DR: баг ядра в подсистеме transparent huge pages (go аллокатор их использует), страницы не мержатся в одну линейную область (VMA).

Для проверки предлагают увеличить значение

   vm.max_map_count = 65530
до чего-нибудь большего:
   sysctl vm.max_map_count=131072
При наличии ошибки ядра количество областей должно со временем расти.
    pmap -p $(pidof csv-parser) | wc -l
У меня не растет и остается равным 18.

Если роста VMA не наблюдается - значит что-то еще утекает.

GODEBUG=gctrace=1

должен показать растет ли размер кучи.

https://github.com/golang/go/blob/master/src/runtime/mem_linux.go#L75 : текущая реализация (с хорошим комментарием) до сих пор использует huge pages с какими-то эвтистиками для подавления роста VMA.

sf ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.