Как изменится размер файла если уменьшить разрядность
Я прошлый раз (по файлу RG1051.DBF) получил квалифицированную помощь (и, надеюсь, исполнитель удовлетворен работой со мной).
Сейчас проблема с 1SBKTTL.DBF.
И он растет с непонятной динамикой.
Например:
1) Тестовая база, сделанная 12.02.22 (но для работы точка актуальности стояла на 26.05) размер файла 1797 МБ.
2) копия базы за 30.04.22 (точка актуальности установлена на 1.06.22) размер файла 1864 МБ.
3) копия базы за 18.06.22 (точка актуальности установлена на 3.07.22) размер файла 1932 МБ.
4) актуальная база на 5.07.22 (точка актуальности установлена на 3.08.22) размер файла 1934 МБ.
Не ясно почему так. Вроде как, между базами 1 и 2 — 1 месяц по ТА и разница в размере файла — 67 МБ. Между базами 2 и 3 — 1 месяц по ТА и разница в размере файла — 68 МБ.
Так почему между базами 3 и 4 тоже 1 месяц по ТА, а разница всего 2 МБ?
Как бороть? С одной стороны — есть задача остановить такой бешеный рост (68 МБ в месяц. 1 год — 0,8 гига). С другой стороны — скоро 2 ГБ и все, база встает.
Обращу внимание на тот факт, что при пересчете итогов база зависает на 4 дня и ни разу (за последние 7 лет) такой пересчет не проводился.
Кроме того, в конце прошлого года «хирургически» резались некоторые регистры (и исправлялись некоторые проводки). При пересчете они могут опять «всплыть» (могут и не всплыть, так как проводки, все же, корректировались).
Надо, как всегда, вчера.
Это бух подсистема вроде.
#==TABLE no 422 : Остатки
# Name |Descr |Type[A/S/U]|DBTableName|ReUsable
T=1SBKTTL |Остатки |A |1SBKTTL |1
#——Fields——-
# Name |Descr |Type|Length|Precision
F=DATE |Period |D |8 |0
F=ACCID |AccountDt Id |C |9 |0
F=CURRID |Currency Id |C |9 |0
F=KIND |Total kind |C |1 |0
F=OBDT1 |Total turnover DT |C |15 |0
F=OBKT1 |Total turnover KT |C |15 |0
F=OBDT2 |Total turnover DT |C |15 |0
F=OBKT2 |Total turnover KT |C |15 |0
F=OBDT3 |Total turnover DT |C |15 |0
F=OBKT3 |Total turnover KT |C |15 |0
я в этм не осбо силен.
Этот файл содержит рассчитанные бухгалтерские итоги остатков и оборотов по синтетическим счетам и объектам аналитики
Рекомендуется произвести «свёртку» базы данных с помощью обработки WRAP.ert. При выполнении это процедуры — остатки свернуться на начало отчётного периода (желательно на начало года). Предварительно обязательно сделайте архивную копию, так как эта процедура не обратимая.
Это что за конфига?
(5) Для чего там делается Выгрузка — Загрузка. Достаточно было в Тестирование-исправление запустить Упаковку таблиц.
Не закрытые остатки по счетам. когда остаток вроде бы и 0 а развернутый до всех субконто где то -1 а где то +1. Все тоже самое что и с регистром.
А скачкообразный рост может быть связан что когда выросла на 2 мд. кто то этот месяц перепроводил и все закрылось красиво. а в остальные месяца забили на это.
RG1051 — это регистр . WRAP.ert на сколько я знаю только для бух итогов по счетам .
на ИС куча универсальных которые могут свернуть на дату .
перейдите на СКЛ -))
к (5) и (7): Выгрузка-Загрузка и ТИИ-Пересчет итогов запустят помимо пересчета бухитогов 1SBKTTL.DBF ещё и пересчеты оперативных итогов запустятся.
Прошлый раз Злоп занимался, базу уже знает, значит ждём его — ему виднее.
к (0) — 1SBKTTL.DBF от точки актуальности не зависит, это поквартальные бух.итоги, и если у тебя в «Операции-Бухгалтерские итоги — Расчет итогов установлен по» стоит что-то кроме «3 квартал 2022 года» — это плохо. Некоторые нелюбители ежеквартального открытия бух.итогов могут двинуть на годы вперёд, что приводит к тормозам и росту объёма.
Так как у тебя проблема с 1SBKTTL.DBF, а не с 1SENTRY.DBF, то скорее всего регулярно не закрываются какие-нибудь объёмные счета — типа 10 или 41 в России, какие у вас — не знаю.
Свертку надо делать. Скорее всего не закрываются полностью счета. А при увеличении бух периода этот файл и растет.
(0) 1SBKTTL.DBF это не регистры, а остатки плана счетов.
>>>Обращу внимание на тот факт, что при пересчете итогов база зависает на 4 дня и ни разу (за последние 7 лет) такой пересчет не проводился.
Если есть RA и бухитоги, то поди ПУБ или комплексная, и там и там нужно выкинуть аналитику на 41 счете к еб..ям, и твой 1SBKTTL будет метров 100 от силы или меньше.
Комплексная там база.
(7) Выгрузка/Загрузка бывает раз в 10-100 быстрее Упаковки, тем более что при Загрузке идет пересчет условно говоря «пустых» таблиц.
(0) Не сильно нервничай, проблема 2Гб решаема:
Для увеличения памяти 1С больше 2Gb: 4Gb Patch NTCore https://ntcore.com/?page_id=371
Для загрузки и выгрузки dt больше 2Gb: ConfigSpy от АЛьФ http://www.dorex.pro/?projects&configspy&download
Для работы 1C с файлами dbf больше 2Gb: dbeng32 от Wirth https://cloud.mail.ru/public/3mVX/4trK45on8
Возможно после Выгрузки/Загрузки что да изменится.
А вот парадоксальное распухание проводок это что-то не так у вас в учете, совсем что-то не так, но не силен в бух учете.
Тут вариантов навалом, первое что вспоминается так это учет в 2х валютах, в 7ке сделано очень плохо — копейки не закрываются, ну и вообще копейки выплывают то там то сям и «отравляют» остатки.
да там вообще можно бух учет настройками отключить и забыть про него как в страшном сне. все равно его никто не юзает если базу не препроводят
(17) тогда отключи аналитику по номенклатуре на 41 счете, она там нафиг не сдалась
(20) я не имею отношения к автору. помню по его декабрьской теме.
Мне когда-то миста помогла. Когда файл близок к 2гб., то свертка уже не поможет. Точнее, поможет, но сворачивать надо чуть ли не по неделе-две. Потому что когда делаешь свертку полностью, то 1С-ка СНАЧАЛА пишет итоги в тот же файл, потом типа начинает очищать старые (собственно, а иначе то как). И для «потом» уже места в файле не находится. Я сворачивал раза три подряд. Сначала 2012-й год (база была с 2011-го), затем с 2012-го по 2018-й и потом уже окончательную сколько там осталось (буху нужны были доки за последний год и все).
(22) (18) (14) (11) (7) (4) Свертка и загрузка-выгрузка не работают. На загрузке-выгрузке безбожно зависает. Свертка периода невозможна из-за огромного размера кода, который ДОПИСАН к 1-С (называется «технологический учет»). Там есть текущие процессы, которые не режутся (в отличие от фиксированных, одномоментных бухопераций). Плюс, есть куча регистров. Попытка обрезать базу была сделана на 1.01.2016. Не совсем успешно (многие регистры не изменились вообще, а многие документы остались помеченными на удаление, но не удалились из-за кучи перекрестных связей). Хотя тогда с десяток гигабайт из базы ушло (обрезчику реальное спасибо).
(11) спасибо. Бухитоги глянул. стоит 3кв 2022 года.
(11) (20) В балансе 41-й счет отсутствует.
(23) На загрузке-выгрузке безбожно зависает — ну так нужно ждать. Процедура не быстрая. Попробуй для начала просто сжать «Тестирование-исправление запустить Упаковку таблиц.»
(25) Ну другой какой то счет видимо.
(23) Нуу, тогда переход на SQL вам поможет. Мне советовали и такой метод.
Сжатие не поможет у него реально файлы такого размера в декабре смотрели. нужно или резать или отключать бух учет или создавать документы и закрывать не закрывшиеся счета. приводить базу в порядок. а не каждые пол года пожары тушить.
(25) очень интересно. А вообще, проводки то хоть кто-то смотрит у вас ? Если бух итоги не еперсчитываются, то поди и в отчетах ад и Израиль и циферкам веры нет. Может, ну его на такую базу ?
Украинская комплексная ИМХО вообще худшая конфигурация из мне известных. А когда ее еще и переписывают как попало то получается вообще темный лес. у нас товары не на 41 счета а на 281 сидят. не должно там быть 41
Да не пользуется там никто бухией. Под комплексную насколько мне известно обновления уже давно не выходят когда она переписана непонятно как обновить бухию даже если и вышло обновление целое дело. сейчас все отчеты электронные без обновлений это мазохизм бух учет вести в древней конфигурации.
По прошлому ремонту регистров там тупо двойной учёт по финансовым и упр учёту как в тис 8.7 плюс тупо не закрывались регистры. Когда ремонтировал по регистрации — после подчистки ненужного там от двух гиг почти с гулькин нос остался мегабайт 200, точно не помню.
По проводкам тогда не смотрел только глазом цеплял — имеется мне что там тоже аналогичная хрень. Надо смотреть. ТС для начала тупо построить ща последний месяц осв с максимальной разверткой по всём субсчетам по всём аналитикам и посмотреть.
А с учётом что украинский Worksection забанил все российские аккаунты. Встаёт аналогичный моральная проблема.
Надо смотреть конфигурацию,так как иногда даже при хорошем закрытии итоги вылазят за 2гб из-за того,что много данных.
У меня последняя 7.7 хоанила итоги только за полгода и все равно вылазила из 2 гб только из-за того,что очень много операций. Я и субконто удалял,переводя на синтетический справрчник (аналог ключей аналитики) и разрядность уменьшал,но когда файл проводок стал к 2 гб подбираться,пришлось уже от бухгалтерии просто отказываться.
(28) не могу сейчас оценить возможность такого перехода. Я не во всем разбираюсь в этой жизни. А как потом конфигуратор дописывать? И если перейти в СКУЛ, то я так понял, что обратного пути не будет. И не понятно — как править что-то хирургическим путем (сейчас еще можно).
(29) очень похоже на то. Как-то в прошлом году пробовал запустить сжатие таблиц. Результат — почти нулевой.
(30) Проводки смотрим. Но смотрим так. вяло. Основное — контроль счетов 20.7 (остатки запчастей), 30 (кассы), 31 (счета), 36.1 (клиенты), 37,2 (подотчеты), 63.1 (поставщики), 70 (доходы) и 80 (затраты).
Еще в проводках используются другие счета, но мы их не очень контролируем.
Эта бухгалтерия — неофициальная и не используется для отчетности. Это СРМ на базе 1-С. Но с учетом прибылей и убытков (по нескольким субъектам деятельности).
(37) если результат почти нулевой — два вариант — или все очень хорошо или сжиать нечего, потому что итоги не закрываются.
с учетом написанного в (38) — там просто тупо куча висящих остатков
Та он тролит и уже не первый раз. Ему еще в позапрошлый раз русским языком было сказано — любая учетная система требует определенной дисциплины. Любая.
Но ему по-барабану, он думает так: зачем напрягаться, соблюдать какую-то условную дисциплину в учете, лучше когда наступит время, найму гарных хлопцев и они мне все почистят, пусть у них голова болеть будет.
Иными словами — вас в очередной раз нанимают, как тупо клинера, то бишь работника клининговой компании.
И не обольщайтесь, это так и есть. Уборщик, он и в Африке уборщик, несмотря на гордое название — клинер.
Мне чисто любопытно, если час программиста 1С стоит условно говоря 2000 руб., то сколько стоит час клинера 1с? Больше? Меньше? Сравнимо? Ну, зв отношение к клинеру мы не говорим, отношение к уборщику оно везде отношение к уборщику, но вот оно хоть того стоит?
(8) (11) подтверждаю, что выяснил, что этот файл «растет» при переводе бухгалтерских итогов (там переход поквартальный). Сейчас я экспериментирую с копией базы за 12.02.22. И при переходе с 1-го квартала на 2-й и со 2-го на 3-й происходит рост файла на 63,5 МБ. А при переносе точки актуальности — размер не изменяется.
Купите уже БП3
(41) Уважаемый гость из Мариуполя.
Не понимаю почему Вы считаете это троллингом?
Если бы у меня было предприятие типа Сбербанка или Аэрофлота — я бы взял миллион долларов и купил бы хорошую, настроенную программу. Но когда предприятие на грани закрытия, то ресурсов хватает только на какие-то полу-меры (и это касается не только бухгалтерии).
С официальной бухгалтерией у нас проблем нет. Все работает, ничего не глючит. Красота. Я туда вообще не вмешиваюсь.
Но конкретно эта программа досталась в наследство и в ней 1-С — это базис, к которому прикручен специальный технологический учет (CRM). И тот человек, который это писал (лет 20 назад), возможно, не все учел. А после него было несколько специалистов (еще до моего присутствия), которые отключали куски кода. Но как отключали и где — никто не знает. Я получил в наследство некоторую программу, у которой что-то работает хорошо, что-то — не очень, а что-то вообще не работает. И мне, директору, приходится вникать в суть программирования и как-то править этот механизм.
При этом, я не соглашусь с Вами, что я нанимаю просто клинера. Я вместе с программистом изучаю код и исправляю его так, чтобы ошибки не накапливались. Вот я за последние 9 месяцев разбирался с 2 регистрами и не просто порезал их, а добился того, чтобы эти файлы больше не собрали мусора. И все путем.
(42) точка актуальности — это оперативный учет, регистры. а бухгалтерские итоги поквартальные — это бухкомпонента.
«Вот я за последние 9 месяцев разбирался с 2 регистрами и не просто порезал их, а добился того, чтобы эти файлы больше не собрали мусора. И все путем.»
— надо разбираться с концепцией/архитектурой того что вам досталось в наследство, а не затыкать дырки.
А-а-а, то есть не просто клинера (а уборка мусора это и есть задача клинера, как не назови), но клинера-сэнсея.
Извиняюсь, что не понял сразу истинные цели и задачи.
(46) согласен. Меня самого не устраивает вариант затыкания дырок. Главное — чтобы для реализации правильного варианта было время. Следующий переход на 4 квартал может быть критичным.
Пока получается наводить порядок по частям. Проявилась критическая ситуация в каком-то блоке — разобрались и навели порядок.
(47) мусор убирать просто когда понято что есть мусор а что нет.
(47) я не совсем понял — что Вы имеете ввиду.
(43) Если это новая платформа, то вариант не подходит. 1-С — только базис. В моей конфигурации основная ценность — дописанная СРМ. Чтобы перейти на другую платформу — надо взять код СРМ и интегрировать с новой 1-С. При этом — еще перенести остатки, незаконченные процессы СРМ. Я пару раз показал программистам задачу — они просто отказались. Даже цену не называли.
(26) (39) Только что запустил Упаковку таблиц информационной базы. Файл как был 1924533 кБ так и остался.
(51) Извиняюсь но CRM это не «специальный технологический учет»
И 1С пишется слитно
(51) >Я пару раз показал программистам задачу — они просто отказались. Даже цену не называли.
Вероятно вы попросили сделать на новой платформе 1С в некой типовой конфе «точно так же как в старой».
За это не возьмутся ибо нет смысла.
Есть множество готовых типовых и отраслевых конфигураций.
На 110% уверен что ваш уникальный лисапед он нифига не уникальный и аналогичные бизнесы давно уже что то готовое используют.
(49) хм.. база 20-летней давности.. Зачем? документы 20-детней давности никакой ценности, кроме ностальгической, не имеют. Даже документы 5-летней давности. Все это — мусор. Все, что старше 5-лет, в нынешних условиях значения не имеет. Даже для налогового кодекса.
взять копию базы и удалить в ней из последних 20 лет 15 все документы. Я не думаю, что у вас на складе остались автозапчасти, приобретенные 5 или более лет назад. Я не думаю, что у вас актуальны какие-то остатки 5-летней давности.
Но на всякий случай оставить в базе последние 5 лет можно. Остальное — удалить.Все приходы-расходы-заказы, все документы и все движения, которым больше 5 лет.
То есть как вариант я говорю вам не страдать ностальгией, а обрезать базу. И тогда еще лет 10-15 протянете. А там — или ишак, или падишах..
для истории и ностальгии исходную копию, разумеется, оставить.
(53)+ CRM это Customer Relationship Management
— система управления отношениями с клиентами
https://solutions.1c.ru/catalog/crm-prof/features
(55) Именно.
Нет смысла дольше 3 лет хранить.
(53) По поводу 1С — спасибо, учту.
Технологический учет — это наше внутреннее название. Так как этот кусок кода описывает процесс выполнения ремонта. (грубо — этапы техпроцесса)
(54) Именно так и просил. Это низкорентабельный вид деятельности и большинство участников рынка обходятся экселом. Некоторые используют отдельные СРМ (специализированные, от производителей). Но такие СРМ не решают задач бухучета (пусть и в урезанном виде).
Я не горжусь этой программой. Она просто есть. Но относительно уникальная.
Не надо упаковок никаких, это же рассчитанные итоги.
В (33) всё сказано.
Выводишь общую ОСВ с разверткой по субсчетам за период 01.07.2022 — 01.07.2022. С забалансовыми!
Тыркаешь в расшифровку каждого счета/субсчета, чтобы вывести оборотно-сальдовую ведомость по этому счету/субсчету (так в типовой), если в АВТ этого нет — то просто выводишь оборотки по каждому своему счету, в том числе забалансовым, также за один день — начало квартала.
Смотришь в каких из них будет больше, ну скажем 5000 строк (можно включить отображение заголовков строк (Меню Вид-Заголовки), чтоб было видно номер последней строки).
Разбираешься — есть ли там реальные остатки или это просто незакрытая аналитика по субконто. По результату уже можно будет давать советы.
>процесс выполнения ремонта
https://solutions.1c.ru/catalog/service-center/features
ммм.. чуток уточню, а то вдруг, все-таки не специалист. Не просто удалить документы, а именно СВЕРНУТЬ и обрезать базу. А то вдруг вы сейчас возьмете и просто удалите документы.
(61) Это бесполезно.
Чтобы решить проблему свертки нужен хороший специалист по 77.
А их сейчас уже хрен найдешь дешево.
Имхо выгрузить в эксель нужные данные по НСИ, остаткам/долгам
И загрузить в готовую отраслевую конфу
Текучку (незаконченные ремонты) дозабить руками
На все старое глубоко плевать, база 77 же остается для истории
А можно озвучить размер 1SENTRY.DBF? А то может он 20 Mb, а мы тут «свёртка», «переход».
(55) Программе — 20 (или больше) лет. У нас на фирме она 16 лет. Вы не поверите. У меня нет никакой ностальгии по этому поводу. Стоял даже вопрос — экспортировать остатки в (например) Эксел. А потом — импортировать в новую, пустую базу. Но у программиста чет все умерло при экспорте. В 2016 году один программист попытался отрезать все, что было до 2016 года. Часть смог отрезать. Часть — не смог. Хоть отрезал около 10 гиг (уже не помню сколько). Но тогда, 6 лет назад, я еще не понимал, что проблема не в общем размере базы, а в размерах файлов таблиц и индексов на уровне 2 гиг. Это я сейчас знаю ))))
Но дело даже не в этом.
(61) вопрос в том, что у нас в этом технологическом учете (извините, буду так называть) кроме бухгалтерских операций (типа банковской выписки, кассового ордера, накладной) есть процесс (ремонт). Он распределен во времени (от одного дня до. 1 года). Он разбит на этапы. К каждому этапу привязаны разные бухгалтерские документы. В любой момент времени около 1500 процессов находятся. в процессе (сорри). И свертка в любой момент времени — нормальна для бухучета, но неприемлема для этих процессов. Не получается разрезать и перенести части процессов из старой программы в новую.
(64) файл 1SENTRY.DBF 480332 кБ
(59) Спасибо. Этим и займусь в ближайшее время. Суть идеи понял.
Главное, чтобы наведение порядка не затянулось до конца квартала ))))
(59) Но наведение порядка может хорошо остановить рост файла. Но уменьшить-то не сможет.
(60) Спасибо! Попробую ознакомиться.
(65) почему? легко. кого интересуют процессы 5-летней давности. легко отрезать и откинуть часть процессов (а часть пусть останется). все равно этот процесс никому не нужен, ну подумаешь, в новой (обрезанной базе) от него останутся какие-то хвосты (документы).
к примеру, приняли вы на ремонт мерседес в сентябре 2015, а в январе 2016 передали его в кузовной, покрасили и выдали заказчику. Пусть у него там была куча документов от приемки, выдачи запчастей, ремонт двигателя, ходовой и т.д. и .т.п. каждое телодвижение сопровождалось соответствующими документами, которые ссылались друг на друга по цепочке. Мы все это обрезали и на 1 января сделали ссылку на служебный документ ВВОД ОСТАТКОВ. (или как угодно обозвать). В итоге у нас документ передача в кузовной ссылается на ввод остатков, а все что до этого — покрыто мраком.
ну подумаешь, будет в новой базе неполноценный (обрезанный) процесс. кого он конкретно заинтересует, тот пусть смотрит в исторической базе.
у вас желание сохранить процесс полностью, всю историю и это понятно. Но почему нельзя обрезать половину истории, половину процесса, кому оно нужно спустя 5 лет?
Ссылку во всех хвостах всех процессов на дату обрезки сделать на служебный документ, который обозвать — «Есть ли жизнь на Марсе? Науке СИЕ НЕВЕДОМО. » и пусть хвосты этих обрезанных процессов болтаются себе в CRM.
Правильно там программисты отказались.
Чтобы прог мог что-то вменяемо сделать — ему нужна не старая база как образец — а описание бизнес-процессов, причем вменяемое. А вот с таким вот описанием — в компаниях — беда. И описания нужны не отрывочные инструкции по участками что и как делается а общая связанная схем от входа до выхода по концепции зачем и почему. а уже пониже уровнем что и как.
.
делать мега-мега-заточку прям вот до мелочи-мелочи именно под свою контору — это надо взять на полгода-год в штат на постоянку прога, который еще и не прог, а в довесок еще и немножко аналитик. Тогда что-то получится.
.
а с учетом того что как написал в этой ветке ТС — он директор и разбирается в этом доставшемся ему наследстве — цельнйо системы там никогда не было. строилась постепенно костыль за костылем, сорри. Это даже по интерфейсу видно.
.
это не есть хорошо, это не есть плохо. это есть как есть, жизнь. Плохо только то, что за 20 лет это не удосужились привести во вменяемое состояние. То есть контора на развитие и поддержку бюджетов не выделяла. И приплыли к тому к чему приплыли.
.
и даже обрезка, которая была сделана по состонию на 2016 год — она сделана была технически, не убрав все «болезни левизны в коммунизмке».
.
поэтому есть два вариант
1. забить и перейти на отраслевоей решение на 8-ке
2. взять хорошего прога семерочника — они еще остались (тут я почешу ЧСВ) — выделить дохера бюджета и провести тотальный рефакторинг и процессов и кода. Тогда на отрефакторенном варианте до 2037 года можно будет без проблем дожить.
Процессы нефига не уникальные и бизнес не везде низкорентабельный. Для того же обслуживания и ремонта техники была специализированная конфа МИФ в 2000 годах не знаю во что на сейчас переродилась. Авторемонт +/- те же техпроцессы и уж что он низкорентабельный язык не повернется сказать. И контор занимающимся им миллион.
Другой вопрос, что сейчас не совсем то время чтобы устраивать переходы на новые платформы. Бух учет и оперативный учет сами по себе не сильно связаны. Самое простое сейчас создать бух остатки на 31.12.17. запретить изменять документы до этой даты и выключить проводки в более ранних документах.
(72) Все попытки остаться на 77 это лишь временная заморозка ситуации
Вряд ли ТС до сих пор ездит на https://ru.wikipedia.org/wiki/Ford_Model_T не так ли
(74) ну в доме постройки 60х годов вполне можно жить. А есть еще дома вообще по несколько сот лет
Я чисто из интереса. А почему базу не перечти на SQL? Это проблему не решает? Я переводил семерку с данным приколом на скуль, все замечательно работало.
(74) В стране война в Мариуполе народ уже рисует русские печатные формы.
(76) По СКЛ железо не потянет. Нужно новый сервак покупать и все перенастраивать при том что в таких сервисах обычно 1-2 девочки на приемке сидят максимум и мастера иногда заходят заказы состояния ремонтов изменяют. Обычно там 20-30 документов в день вообще не объем при прямой базе.
Я бы для начала посмотрел на проблему незакрытых остатков. Можно накидать отчет вида: остатки по счету в целом и остатки по всем аналитикам этого счета. И уже вручную проаналитизовать, где развернутое сальдо.
(70) Вы сейчас мне подкинули одну идею насчет обрезки. Я над ней подумаю. ОГРОМНОЕ СПАСИБО. Только я чувствую, что обрезка — это, все равно, не полноценное решение. Ибо если что-то где-то неправильно делается из-за неправильной дисциплины учета (как Вы же правильно и сказали. ) то даже в новой, обрезанной базе начнут накапливаться новые проблемы. А их надо победить ПРИНЦИПИАЛЬНО!
Еще раз спасибо за подсказку. Вы меня натолкнули на интересное решение. Всегда важен взгляд со стороны.
Но пока я буду правильное решение реализовывать, могу упереться в 2-гиговый файл 1сбкттл. Вот тут бы я подстраховался бы как-то.
(76) сисадмин не тянет этот момент. Он приходящий и делает все по минимуму. А запускать скул — это для него ЗАДАЧА
Сейчас на работе в запаре. Смогу ответить вечером.
(78) Та вообще.
Прежде чем анализировать всякие развернутые сальдо, я бы для начала посмотрел вообще на всю аналитику по всем счетам, и, если она не нужна в принципе, то удалил бы лишние субконто у счетов нафик вообще. С ТИИ и пересчетом итогов, разумеется. Чтобы не анализировать и не сворачивать ненужные остатки и обороты по ненужной аналитике, проще их вообще убить. Нет тела — нет дела. 🙂 нет субконто на счете — нечего и анализировать.
А Если какая-то аналитика все-таки нужна, но не закрывается, то, следовательно, ему нужны не остатки в разрезе этой аналитики, а всего лишь обороты по этой аналитике. Такие субконто можно посмотреть и может быть сделать.. оборотными..
Ну и только та аналитика, которая ему нужна в разрезе остатков.. хм.. он и так говорит, что некоторые счета и аналитику по ним они контролируют. Видимо, как раз ту, которая ему и нужна в виде остатков.
(80) Вот это и будет ПРИНЦИПИАЛЬНОЕ решение. Убрать всю ненужную аналитику вообще.. из плана счетов..
Правда, придется очень внимательно все документы проверить и переделать их проведение только на предмет нужной аналитики. Что тоже работка ой-ей-ей немаленькая.
Если тебе не хочется ни на скуль, ни на 8-ку, а хочется именно решить вопрос принципиально именно с этой конфигурацией, то принципиальное решение, не позволяющее даже теоретически получать лажу, такое — убрать вообще ненужную аналитику из плана счетов, нужную исключительно в части оборотов сделать оборотными субконто, а остальную нужную (по остаткам) отслеживать и закрывать каждый месяц.
Та собственно говоря, убрав ненужные субконто по счетам, модули проведения документов нужно будет править по мере необходимости. Смутно подозреваю, что там в большинстве случаев придется лишь закомментировать строки проводок в части удаленных субконто.
ну и где нужно, порядок субконто откорректировать..
Глянул файл автора. 65% первых 100 тыс записей этого файла это 24 счет «брак в производстве» который как понимаю никогда не списывали.
Еще 26% приходится на счет 29.1.1 это запчасти в готовых изделиях гарантия. Кто придумал гарантию на счетах хранить хз. как минимум сразу +/- делать и искать не по остаткам а по оборотом. Брак скорее всего достаточно с определенной периодичностью просто списывать. вряд ли вам нужно знать остатки брака за 15 год.
А по гарантии нужно менять логику и отчеты которые ее юзают.
(83) пересчет итогов у автора зависает и 7 лет никто не делал. там ручками придется файл вычищать.
(83)Лесть и править всю аналитику и править проведение ради оставшихся 9% объема смысла нет.
(88) ну у меня базы нет, поэтом конкретно по счетам я глянуть не мог. Если все как ты говоришь, то да, не спорю.
(87) Итоги по регистрам пересчитывать не надо! у автора распухнет ранее леченный регистр. так как итоги будут по движухе строится, а движуха до декабря прошлого года — нелеченная.
(90) Если я это буду делать гляну по базе, если будут вопросы стукнусь уточную.
По модулю проведения вроде все красиво запчасти и брак в конечном итоге должны на затраты списываться, но видать не все субконто совпадают раз он так пухнет.
(92) вот про это я и имел в виду когда говорил про рефакторинг. можно позатыкать дырки, а можно систему в чусвто привести. от бюджета зависит
(93) Может там и с кодом все нормально и просто нужно регламентные работы проводить регулярно и нормально документы вводить. Есть у меня клиент база нормально написана все ок должно быть, но полный бардак. Нужно править и заносить документы задним числом и все тут. Причем потрудиться и разобраться что приходную нужно внести 1 а не 2 числом никто не хочет. препроводить базу хоть иногда тоже. В итоге в отчетах видят полную фигню и говорят напиши нам базу так чтобы работала красиво и правильно. И удивляются, что я не хочу за это браться, мы же денег заплатим просто скажи сколько.
(66) Файл проводок должен быть раза в 2 больше файла итогов, ну или того же порядка. То есть 1SBKTTL.DBF раздут в 10 раз.
Ну и в целом понятно — проблемные счета выявлены, решения зависят от логики работы программы.
(92) Наверняка можно сделать субконто по 24 счету оборотными, если он под ноль ежемесячно списывается, а по субконто не закрывается. Тогда и проблема уйдет сразу.
Если да, тогда для быстрого пересчета можно на копии удалить 1SBKTTL.DBF и RG* RA*, потом сделать субконто оборотными, пересчитать бух.итоги и подсунуть уже в рабочую базу. Ну это уже надо код видеть.
Привезли внука. Это КАТАСТРОФА. Приношу извинения, что не отвечал и не реагировал. (((((((((((((((
(95) Изучаю проводки и оборотки по 24 счету.
Вижу явные бока именно по 24-му счету. Там и партия «теряется», и дата проводки неверно формируется при списании с 24-го.
Буду думать — как исправлять.
Можно попросить Вас пояснить: что значит «сделать субконто по 24 счету оборотными»?
Примечание: сч 24 под ноль ежемесячно не списывается. Там постоянно есть (должно быть) сальдо.
По одному документу (Внутренняя расходная накладная) запчасть списывается со склада (счет 20.7) в текущий ремонт (счет 24). А по факту окончания ремонта (документ «Ремонт» получает статус «готов») запчасть должна быть списана с 24 счета в 29.1.1. «Запчасти в готовых гарантийных ремонтах». При переходе с месяца на месяц есть большое количество ремонтов «в процессе». То есть, запчасть со склада выдана в производство, но производство еще не закончено и запчасть висит на 24-м счету.
В конфигураторе открываете план счетов клацаете по партиям и в открывшемся списке выбираете только обороты. Только боюсь он уйдет в пересчет итогов после этого, а если они у вас не считаются.
По итогу конда вы смотрите оборот за период у вас будет
Склад Пети — шестеренка- Приходная . -10 шт.- 300 грн.
А когда смотрите остатки
Склад Пети — Шестеренка — 10 шт -300 грн. В остатках партии свернулись
То что есть незакрытые ремонты и сальдо на счёте при переходе месяца не проблема если это в итоге закроется. А вот если не закрывается или остаётся какая-нить 1коп незакрытая из-за кривых рук костылестроителей то вот вам и жопа
(97) Если партия при списании теряется и это никого не парило (возможно партионный учёт не нужен на сч.24, возможно он корректно ведётся в оперативном учёте и дублировать его по бухсчетам смысла нет), то субконто «Партии» можно сделать оборотным.
Второй вариант: сделать ежеквартальную закрывющую операцию со сверткой аналитики по закрытым партиям.
Д24 Пустая партия Д24 Закрытая партия
Третий вариант: не спеша по ночам или в фоновом режиме пробежаться по проводкам по сч.24 за старые года и занулить субконто «Партии».
Прелесть бух.компоненты в том, что в отличии от оперативных регистров можно без извращения с технологическим перепроведением или прямым лазанием в DBF в рабочем режиме штатными средствами менять проводки у проведенных документов как хочешь. Любые из вариантов лучше попробовать сначала на копии, потом попробовать «Операции-Бухгалтерский итоги-Полный пересчет итогов». Все три варианта по итогу должны сильно ужать 1SBKTTL.DBF
Но всё равно без анализа конфигурации я бы не стал рекомендовать ничего делать, любые варианты всё-таки требуют определённой квалификации.
Больше наугад невозможно — стоит поискать спеца в доступности (uno-group?)
Тогда субконто партии вообще убрать если оно в оу есть
Средневзвешенная цена рулит. Партии буквоеды придумали.
(99) я так понял, что там вообще не закрывается ни один гарантийный ремонт правильно. Списание с 20.7 (склад) на 24-й счет идет с указанием партии. А с 24-го списывается не по партии, а по строке < >(я не понимаю что это, но похоже на партию по умолчанию). В результате, по всем закрытым гарантийным ремонтам на 24м счете есть +1 и -1 по двум разным строкам (по партии и по < >). Более того, списание с 24-го счета делается не в момент окончания ремонта, а задним числом (когда ремонт создан). Не понимаю логики такого. Ведь у документа «Ремонт» есть конкретное поле — «Дата готовности» и очевидно, что списание с 24-го счета должно быть датой готовности. А сейчас по учету деталь с 24 счета списывается раньше, чем выдается (хотя реально операция списания проводится позже). И вот это плодится и размножается с момента начала работы. Даже те ремонты, что уже попали под обрезку, оставили после себя это «счастье». И очень прикольно это выглядит для ремонтов, которые были созданы до даты обрезки (1.01.16), а запчасти по ним выдавали после даты обрезки. По таким ремонтам списание со склада на 24-й счет есть (так как накладная создана после 1.01.16), а списания с 24 нет, потому что дата создания ремонта — до 1.01.16. (где тут смайлик «рука-лицо». )))))
(98) Спасибо огромное! Попробую на копии. Все пробую на копиях. Их уже как собак нерезанных ))))
(103) это ровно до того момента когда курс не скачет как бешенный и когда на складе преимущественно одна партия закупки. Когда оборотных средств достаточно, фин подушка есть — парт онка не особо нужна
Строка субконто со скобками — это незаполненное субконто или заполненное субконто но заполнен на карточка субконто неверно, например не введено наименовпние
(104) мударасы писали и результат мудараский. Точно такая же картина как по регистрам в оу
(98) Значит ли это, что партии (субконти) пропадут по всем счетам? Или можно сделать, что партий (субконто) не будет на каком-то конкретном счете?
(106) у меня не отражаются сообщения 101-102-103
(106) Именно когда курс скачет как бешённый средневзвешенная цена и рулит. У тебя себестоимость плавно растет и плавно снижается И фин результат твоей деятельности нормальный и цены нормальные и продажи не скачет вслед за курсом. сегодня грузим фурами завтра курим бамбук.
(110) клацнули не по 2 странице а по треугольнику при заходе в тему
(109) на конкретном
(104) Кстааати. Не все так однозначно плохо. Есть еще хуже. ))))
По тем ремонтам, которые попали под обрезку, ситуация очень неоднозначная. На 24 счету:
По ряду ремонтов есть +1 -1.
По ряду ремонтов только -1 (с ссылкой на < >или на Авансовый отчет или на Приходную накладную)
По ряду ремонтов только +1 (с ссылкой на Приходную накладную)
По ряду ремонтов сумма зачисления на 24-й отличается от суммы списания (хотя количества совпадают).
(101) Спасибо. Где-то в этом направлении и двигаюсь. Со спецом начинаю работать. Сам нифига не сделаю. Но пытаюсь понимать — что как устроено, как должно быть устроено правильно и какие действия для этого надо выполнить.
(103) мы до сих пор должны отчитывать перед производителями использованные в гарантиях запчасти в привязке к партии. В отчетах указываем номер их отгрузочного документа. Только сегодня переуточнял. Все именно так. Поэтому пока от партий отказаться не можем.
(116) Причем тут одно к другому. Пишите эту информацию в документ серийный номер используемой при ремонте запчасти. строите черный запрос по документам для своего отчета производителю. Я начинал свою карьеру в дилерском центре Canon лет 12 отработал без всяких партий все работает. В партии у вас есть только приходная накладная а по 1 приходной может прийти 100 запчастей с разными серийными запчастями и производителю одной приходной все равно мало они просят еще и номера. Так что с 1 счета вы все равно инфу не вытащите только документ потом начинается перебор строк документа или подчиненного документа.
По хорошему там не партия должна быть а серийный номер.
(118) у наших запчастей нет серийного номера. Вы правильно говорите: есть немало ситуаций, когда приходит несколько одинаковых запчастей. И у них нет серийника (подшипник, сальник, термостат, амортизатор, тен и т.п.). Мы в отчет вписываем только номер документа отгрузки поставщика, который привязан к нашему номеру партии. Этот номер документа и вытягивается в отчет через наш номер партии. Пока у нас так оно работает. И боюсь, что я не готов как-то пересмотреть текущую схему. Вынужден пока работать с партиями (при том, что в другом бизнес-проекте по торговле мы замечательно работали без партий и я видел как это делалось).
(117) бренды у себя ведут некоторый виртуальный склад каждого АСЦ, отслеживая отправки и списания. Возможно, так они стараются избежать злоупотреблений, когда отправили на АСЦ 3 запчасти, а АСЦ списывает 5 (и требует за это оплату). Проверка неидеальная, но она возможна. И это не наши правила игры.
Если у вас в документе списание в ремонт или как он там зовется не выбрана вообще никакая партия то она у вас по факте не используется. а в отчет вообще с потолка тянется.
не может быть в проводке по счету пустая партия а в отчет пестрёном по этому счету подтягивается правильный приход.
(121) я с Вашей логикой полностью согласен. Надо проанализировать как подтягиваются номера приходов. Но я один лично сегодня видел (бегло). Придется глубже копнуть.
(121) в самом документе, который отгружает запчасть со склада в производство (на ремонт), есть указание партии. Документ называется «Внутренняя расходная накладная». И там партия выбирается. И она же проводится по счету 24 с указанием партии (и там зависает). А вот списание с 24-го проводится без указания партии (почему-то). Вот так сейчас и возникает «пересортица» на 24-м.
Но это я еще не смотрел — как вытягивается номер приходного документа в отчет. Обязательно посмотрю.
в 2016 году выловил еще одну красоту.
По внутренней расходной накладной запчасть списывалась со счета 20.7 (склад) на 24-й счет, а по факту постановки в готовность стоимость запчасти списывалась на счет 29.1.1 с. 23-го счета.
Просто нет слов.
а причем тут 24 и отчет о гарантийных запчастях?. Я так понимаю, информация о гарантийных хранится на счете 29.1.1 и оттуда по идее и должна браться в отчет, не? вот там на 29.1.1 партии и нужны. Раз у вас там гарантийные ремонты хранятся.
а на 24 у вас же списываются не только гарантийные, но вообще все ремонты? вся сборная солянка.
и опять же, вашему поставщику неинтересно, по какой цене (с какой наценкой/накладными расходами) вы оприходовали запчасть на склад и потом списали в производство, его интересует цена, по которой он вам запчасть поставил. Может, отсюда на 29.1.1 и тянется цена не с 24, а с 23 счета?
(125) А я и не утверждаю, что счет 24 напрямую связан с отчетом о гарантийных запчастях. И я не готов сказать, что ВСЯ информация о гарантийных запчастях хранится на 29.1.1
Но судя по проводкам с 24 (23) на 29.1.1 идут проводки без указания партий. И как-то оно пока работает (в отчет попадает информация про партии). Пока не готов сказать — как.
Вы правы: поставщику не интересно какие были накладные расходы. Но компенсирует нам он цену из своей накладной. Если по одной накладной запчасть пришла за 100 грн и мы ее поставили, то в отчет попадет (и будет компенсировано 100 грн). А если по второй накладной запчасть пришла за 90 грн, а мы попытаемся подать в отчет за 100 грн, то нам скажут: «не шалите ребята. Мы вам запчасть по 100 уже компенсировали.» Поэтому они и следят за складами АСЦ (и судя по всему, у себя тоже ведут партионный учет наших складов).
Я сейчас пришел к выводу, что база где-то бочинит. Так как одни и те же ремонты проводит по-разному. Вот я только что взял 2 ремонта с разницей в месяц. В каждом ремонте проводка Дт 29.1.1. Кт 24 (то есть, запчасть списывается с 24 счета). Но в одной внутренней расходной накладной запчасть закидывается на 23 счет, а во второй — на 24. Вот и буду разбираться — что заставило базу так поступить.
Да это кладовщики/менеджеры разные.
Один умный, сцуко, и «знает», что запчасти надо списывать в производство (счет 23 производство, аналог нашего 20-го счета),
а второго выучили, как обезьянку, что в вашей НЕ типовой конфе счет 24 (Брак в производстве) используется НЕ типовым способом, НЕ как брак, а как учет запчастей, выданных в ремонт. Вот они во внутренней расходной накладной и лепят, кто во что выдрессирован.
А там в накладной может какой реквизит стоит (типа брак/не брак) галочка), вот один эту галочку ставит, а другой наоборот, снимает. :)))
(127) Видно опытного телепата и детектива)
Я с бюджетной так же могу, в хоз.учете только по верхам.
та в бюджетке вообще легко (в БГУ1) 🙂 там, к примеру, как только начинают ныть, что у них 737 «не идет», так первым делом смотри кассовые поступления/выбытия с видом операции «прочие поступления/выбытия» сто процентов в табличной части забалансовый 17/18 тиснули, да еще убрать не умеют (Shift F4 нажать). :)))
У ТС бухпроводки — вторичные. В основе лежит оперучет на регистрах. Могу ошибаться, но это то что помню когда смотрел краем глаза.
А почему и как проводки строятся и почему в одном случае так А в другом иначе — в тех тоннах кода который накостыляли надо отладчиком смотреть конкретные ситуации.
Да и ТС для начала хорошо бы хотя бы для себя нарисовал схему учёта/процессов как оно должно быть не привязываясь к тому что есть сейчас. И потом проще это всё отрефакторить чем дыры затыкать. Тем более что это затыкания дыр все равно приведёт к тому же рефакторинга, только выполненному не цельно, а кусочно. И затраты на переделку кода по правильному по цельному рефакторингу будут не такие уж неподьемные, ибо тупаа хуча верхнего слоя возможно останется нетронутой то что касается црм и взаимодействия с покупателями услуг.
(0) Насколько я помню, таблицу итогов можно удалить (вместе со всеми индексами), и тогда запущенный пересчет итогов пройдет легче и быстрее.
(132) следует уточнять про какой пересчет итогов идет речь — про БУ или ОУ.
ОУ — у ТС пересчет итогов запускать не следует. так как движения за прошлые периорды у ТС кривые по ОУ и при пересчете итогов слетит нафиг леченное прошлый раз. писал об этом выше.
(133) ТС вроде на БУ жалуется. В 77 оба учета можно пересчитывать по раздельности.
Следующим этапом ускорения пересчета, это запускать это все на виртуальном диске, включая временные папки. Очень существенно ускоряет процесс, даже относительно SSD. Память сейчас не так дорога, как в прежние времена. Я когда-то пользовался RAMDrive (или RAMDisk?).
А вообще, смысл лечения ОУ, если итоги в результате кривые? И не путаете ли Вы перепроведение, с пересчетом итогов? Я кривые итоги ОУ лечил созданием спец.документа корректировки остатков, который закрывал все не нужные итоги, хоть каждый месяц. Алгоритм расчета лишнего смотрится персонально. Очистку и перепроведение типовые в нем отключать, чтобы случайно после не запустить.
Если очень хочет оставаться на 77, и нужны все движения за прошлые года, при больших объемах, то можно переделать хранить желаемые движения в какой-нибудь внешней SQL базе. Прописать выгрузку в таблички, и запросы расчета итогов. Ну и потом в отчетах это цеплять.
Что-то я увлекся, я 77 больше не занимаюсь. Не за разумные деньги.
(126) Если (Квитанция.Гарантия = 1) И (Квитанция.Гарантия_Повтор = 1) Тогда
Проводка(Контекст,»24″,ТМЦ.Счет,СуммаУчет,»Выдача детали на повторку»,Кво, Склад,ТМЦ,Партия,
Склад,ТМЦ,Партия, ,,»РМ»);
Проводка(Контекст,»91″,»24″,СуммаУчет,»Списание детали на произв. затраты»,Кво, Константа.БазВидЗатрат.
Склад,ТМЦ,Партия, ,,»РМ»);
Иначе
Проводка(Контекст,»23″,ТМЦ.Счет,СуммаУчет,»Выдача детали»,Кво,Константа.БазВидЗатрат.
Склад,ТМЦ,Партия, ,,»РМ»);
КонецЕсли;
Как понимаю в документе есть галочка какой это ремонт гарантийный или нет и он соответственно проводится или на 23 или на 24 счет.
Отчет жесть жестокая выглядит как то так.
ДокКв=СоздатьОбъект(«Документ.Квитанция»);
ДокКв.ВыбратьДокументы(НачДата,КонДата);
ДокРасх=СоздатьОбъект(«Документ.ВнутренняяРасходная»);
ДокРасхОбщ=СоздатьОбъект(«Документ.ОбщаяВнутренняяРасходная»);
ДокРем=СоздатьОбъект(«Документ.Ремонт»);
ДокВыд=СоздатьОбъект(«Документ.ВыдачаАппарата»);
ДокЗак=СоздатьОбъект(«Документ.ЗаказНаОтправку»);
ДетЗап = СоздатьОбъект(«Справочник.ТМЦ»);
ТМЦЗап = СоздатьОбъект(«Справочник.ТМЦ»);
ПарЗап = СоздатьОбъект(«Справочник.Партии»);
к=0; ОбщаяСумма=0;
Пока ДокКв.ПолучитьДокумент()=1 Цикл
Иначе
Если ФлагОтборДатаПолучения=1 Тогда
Перейти ~r1;
КонецЕсли;
Если ФлагОтборПост=1 Тогда
Перейти ~r1;
КонецЕсли;
Если ФлагОтборДатаВыдачи=1 Тогда
Перейти ~r1;
КонецЕсли;
Кол=Строка(ТекРем.НужноеКоличество);
КонецЕсли;
Если ДокВыд.НайтиПоНомеру(ДокКв.НомерДок,Дата(0))=1 Тогда
ДатаСпис=Строка(ДокВыд.ДатаДок);
Если ФлагОтборДатаСписания=1 Тогда
Если ДокВыд.ДатаДок <> ДатаСписания Тогда
Перейти ~r1;
КонецЕсли;
КонецЕсли;
Если ФлагОтборСписан=1 Тогда
Если ((ДокВыд.ДатаДок < ДатаСпис1) ИЛИ (ДокВыд.ДатаДок >ДатаСпис2)) Тогда
Перейти ~r1;
КонецЕсли;
КонецЕсли;
СостояниеДетали=»Списана»;
Иначе
Если ФлагОтборСписан=1 Тогда
Перейти ~r1;
КонецЕсли;
Если ФлагОтборДатаСписания=1 Тогда
Перейти ~r1;
КонецЕсли;
КонецЕсли;
Какие нафик счета, регистры о чем вы говорите. Черные запросы по документам в самом жестоком исполнении.
(135) Да, такая галочка есть. Там даже 3 галочки есть. Если не стоит галочка «гарантия» — аппарат платный. Если галочка «гарантия» стоит — то проверяется еще 2 галочки: если стоит галочка «по фирме», то тогда эту гарантию оплачивает производитель (и такой ремонт имеет тип «гарантия по фирме» или «Предторговый»). Если стоит галочка «по предприятию», то тогда эту гарантию оплачивает сам СЦ (и такой ремонт имеет тип «повторный»).
(136) А тут я ничего не понимаю ВООБЩЕ ))))))
А еще не понимаю — что значит ЧЕРНЫЕ ЗАПРОСЫ В САМОМ ЖЕСТОКОМ ИСПОЛНЕНИИ ))))))
(133) поддерживаю. ОУ пересчитывать нельзя. Ибо тот аппендицит, который качественно вырезали ранее, вырастет опять и положит базу (более 2ГБ).
(134) А как пересчитывать БУ отдельно от ОУ — не подскажете? Буду очень благодарен.
(127) Я, почему-то уверен, что дело не в менеджерах. Менеджер имеет перед глазами документ Ремонт (определенного типа) и делает одни и те же действия (формирует Внутреннюю расходную накладную на основании документа Ремонт) независимо от того — какой тип документа.
Я сейчас посмотрел этот документ — там никаких галочек нет. И 3 менеджера (разного уровня стажа) подтвердили, что внутренняя расходная накладная формируется одинаково для любого типа ремонта.
Я так понял, что проводки выбираются в зависимости от того — какой тип ремонта (платный, гарантийный или повторный). Но я точно знаю, что за 16 лет в документах Внутренняя расходная накладная, Ремонт, Выдача и Расходная накладная блоки проводок несколько раз перепахивались (не под моим контролем, но я видел, что такие процессы были). И я не исключаю того, что в какие-то года база делала одни проводки, а в другие года — другие проводки.
(134) спасибо за идеи с рамдрайвом. Промониторю этот вариант.
(134) рамдиски для баз для 77 практически не дают выигрыша. максимум что имеет смысл — положить туда темпы для 77
(134) «Я кривые итоги ОУ лечил созданием спец.документа корректировки остатков, который закрывал все не нужные итоги»
— осталось написать формальные правила для автоматического определения «не нужных итогов»
(136) «Черные запросы по документам в самом жестоком исполнении.»
я в этом практически был уверен, ибо строить что-то по той кривизне что в ОУ — страшно.
(142) Дают, существенный. И не только для пересчетов итогов. Если у Вас не получалось, значит Вы не то делали.
(143) Они персональны к ситуации, и соответствующая обработка делается за день. Только выполняется бывает долго в зависимости от запущенности ситуации. А то, что там «почистили» итоги путем удаления записей из таблицы итогов, это положили крест на всех будущих пересчетах. А периодические пересчеты они оптимизируют эту таблицу остатков.
(139) Оно там точно есть, но детали вспоминать это нужно открывать программу. Смотрите в тестировании в конфигураторе, и кнопка пересчет бух.итогов в предприятии.
А в ускоренной с помошью рам-диска 7.7 только один пользователь работал или как это было организовано?
(146) Это по желанию. У меня не было желания сажать несколько десятков пользователей на рамдиск, когда еще и сервер раз в Н месяцев вылетал (не от меня зависило). Ну а сам в рамках конфигурирования очень регулярно так делал, если было не лень подготавливать копию — оно же до ближайшей перезагрузки.
рамдиск отгрызает существенно памяти — если на сервере, значит минус памяти терминалам.
(148) ну так Злопчинский, скорей всего, имел ввиду, что при многопользовательском доступе по сети эффект от использования рамдиска не получится заметить
А вот в терминальных сессиях эффект заметный, когда оперативы в избытке 🙂
(149) хм. Это удивительно, что 77 еще гоняют по сети. Конечно это будет узким местом. Я думал у всех уже давно терминалы. Надеюсь, там пересчет итогов делали не по сети?)
Появился вот еще какой дилетантский вопрос:
Решили мы ПОПРОБОВАТЬ вручную закрыть некоторые «висяки». Нашли 10 незакрытых (на 1.01.2016) балансов по 36.1. Создали операцию 1.01.2016 и позакрывали эти балансы в ноль. После этого посмотрели на 1сбкттл. Он вырос на 7 кБ. То есть, мы обнулили несколько балансов, которые висели с 1.01.2016, но обнуление не уменьшило 1сбкттл. Даже увеличило. Есть предположение, что либо я что-то не так понял, либо для того, чтобы был эффект от этого закрытия — надо сделать пересчет итогов.
(151) Возможно не все субконто учли. В любом случае, таблица итогов точно не уменьшится — убранные записи помечаются не действующими, но не удаляются. А если какой-нибудь хоть один итог добавился, то программа не будет выискивать такие помеченные записи, а просто добавит в конец новые записи. Поэтому нужен пересчет итогов. Или возможно упаковка таблиц. Наверняка не помню, проверяйте.
+ и сложно наверняка сказать, что программа аккуратно, оптимально и последовательно пересчитывает итоги в режиме изменения записей. Может промежуточные итоги какие-нибудь создаются. Это в режими пересчета она строго последовательно это делает, а в рабочем режиме хз.
(153) (152) у меня были похожие мысли
(151) конечно, после того, как вы что-то закрыли 01.01.2016, после этого надо пересчитать итоги. БУХГАЛТЕРСКИЕ.
и упаковать таблицы.
но, честно сказать (несколько у вас это сколько? пусть будет десяток). за 6 лет это 72 помесячные записи на каждый итог. на десяток 720 записей. пусть у вас свернулись все ПЯТЬ субконто (хотя вряд ли) (что такое баланс? в вашей терминологии)
итого — максимум 3600 записей.
сколько там у вас записей в таблице? к миллиону приближается? ну вот три тысячи из миллиона сократили.
или 0.3%
Капля в море.
(155) зато научится понимать, что внутри происходит.
(152) ну, вообще то 27 релиз пользует «зеленые технологии» (то есть повторно использует помеченные на удаление записи), но фишка в том, что у него и так все таблицы максимально упакованы, то есть помеченных на удаление записей особо то и нет, так что не разгуляешься.
(145) «значит Вы не то делали.»
— расскажи что надо сделать, можно повторить.
(149) я хз, по сети у меня в основной компании никто лет 20 не работает, все в терминалке.
(158) Я не знаю, что Вы делали не то. Может временные файлы оставили на хдд. Как устанавливать рамдиск, Вы можете найти в интернете, я его не устанавливал лет 10. Относительно хдд прирост очень существенный. Смотрите, если процесс 1св77 занимает не 100% от ядра при расчетах, значит значит упирается в дисковые операции, и на не достающие проценты ускорится при применении рамдиска.
(157) Если там все упаковано максимально, то пересчет итогов не даст дальнейшего уменьшения размера, а только оптимизацию запросов. Но я сомневаюсь, что эти технологии применимы к таблицам итогов. На помеченные записи нужно строить индекс. Если это справочники или документы, то там не большая нагрузка. А таблицы итогов, тут сложно сказать. Если уверены в этом, то пусть будет по Вашему.
(145) Когда прошлый раз делалось было нужно на вчера и так чтобы оно за выходной отработало. там по другому не сделать было в установленный срок и ограничения.
Вы перекосы за 16 год в 16 году закрыли или в текущем времени? Если в настоящем то все записи итогов так и должны были остаться если в 16 должно было бы уменьшиться только после того как их убрали нужно делать сжатие таблиц без этого она не уменьшится там просто записи пометились как удаленные и все.
(155) Я прекрасно Вас понимаю! Речь шла про эксперимент, который должен какой-то индикатор засветить. Конечно эти 10 сальдо — капля в море.
Я думаю, что это даже не 0,3%, а намного-намного меньше, так как бухгалтерские итоги (вроде) поквартальные и за год — это 4 итога, а не 12.
(161) перекосы (10 субконто) закрыли 1.01.2016 года. Но сжатие (или пересчет итогов) не делал. Просто оценил размер файла 1сбкттл.
(151) если вы там реально что то закрыли в 16 году, то будут нулевые записи в табличке итогов за все периоды что после этой даты. Их или прибить прямым запросом и сжать табличку итогов потом, или делать пересчет бух итогов, и желательно, прибить 2 таблички бух итогов перед этим. Вот тогда, размер должен уменьшиться.
(164) я понял Ваш тезис, что остались нулевые записи после этой записи.
Но не понял — что значит «прибить прямым запросом». Но думаю, что специалист, который будет мне помогать, понимает о чем речь.
Спасибо за подсказку!
(165) «прибить прямым запросом» это типа
Удалить все записи в таблице итогов где значения всех ресурсов = 0
(166) Ага. Тогда это точно не я.))))
Вчера вечером удалил 1сбкттл дбф и сдх. Запустил реиндексацию и пересчет итогов. Реиндексация прошла за минут 15. А потом начался пересчет бухгалтерских итогов. И как в 23-00 зависло на дате 8.08.2017 — так до сих пор и висит.
«И как в 23-00 зависло на дате 8.08.2017 — так до сих пор и висит.»
это просто строка статуса не обновляется, смотрите как размер дбф файла растет
(168) На современном железо и оси оно часто именно виснет
Мне приходилось в виртуалке на winxp/win2k3 это делать на 77
(169) занятно
(166) А какой смысл пересчитывать итоги не исправив косяк?
Всё ж сказали уже:
1) спец
2) Двинуть бух.итоги на начало периода (чтобы перезапись операций происходила быстро) — все спецы это знают
3) 20 строк кода: — спец без проблем справится
а) Опер.ВыбратьОперацииСПроводками(,’31.12.2021′,»24,23″); — текущий год пусть будет корявым, может вам обороты по партиям дорогои
б) Если среди проводок операции есть проводки с 24 или 23 счетом и субконто (третье же?) «партия» заполнено — то зануляем субконто: Опер.Дебет/Кредит.Субконто(3,0);
в) Если в операции были зануления — то Опер.Записать;
г) Запись операций делать порциями 200-1000 штук в транзакции (опытным путём посмотреть, с индикацией скорости обработки в строке состояния);
4) Двинуть бух.итоги на конец периода (вот теперь после исправления косяков с незакрытыми итогами пересчет пойдет быстрее)
5) Для контроля строки состояния использовать ConfStat.exe и все спецы это знают — ничего там не виснет, просто строка статуса не обновляется в современных виндах.
Не надо никаких прямых запросов, это не RG-RA.
ЛИБО
Оборотное субконто — спец знает.
НО
Стоит же минимальный рефакторинг кода делать — завязано ли что-то в проведения документов или в отчетах на САЛЬДО по сч.24,23 в разрезе партий.
Всё.
Всем — по пирожку!
А злопы, которые экономили на спецах — должны страдать.
(171) Спасибо!
Это просто эксперимент, который крутится параллельно основному процессу. Запустил и кручу (заодно проверяю — не зависнет ли пересчет итогов).
Основной процесс будет такой:
Делать будет СПЕЦ. С одним из исполнителей уже договорено. Выбираю между вариантами, про которые Вы пишете. Есть ощущение, что сальдо на счетах 23 и 24 не дороги от слова «совсем». И, скорее всего, переделка проводок будет такая что ВСЁ будет сливаться на 23-й счет без субконто и оттуда — на 29-й (и тоже без субконто). Тем более, что я поднял архивную базу (до обрезки) и выяснил, что в самом раннем периоде и платники, и гарантии проводились через 23-й без субконто. 24-й использовался только для повторных ремонтов (2-3% от общего количества ремонтов). Как потом это распространилось на гарантийные ремонты — мрак, покрытый тайной.
А что такое рефакторинг кода в данном контексте (а то я имею только очень общее понимание этого термина)?
(167) а второй файл итогов оставили? Ну п..ц
(175) ))))) так я же дилетант. Вы уж извините. ))))
(175) поэтому я реальные задачи поручаю специалистам. Сам ничего не делаю кроме экспериментов на копиях.
(176)
1SBKTTLC Содержит рассчитанные бухгалтерские итоги оборотов между синтетическими счетами.
1SBKTTL Содержит рассчитанные бухгалтерские итоги остатков и оборотов по синтетическим счетам и объектам аналитики.
(178) Спасибо.
(168) Есть же специальные приблуды, чтобы читать статус когда 1с замерзает — я использую Abadonna’вский ConfMessages.
(169) Хм, у меня не виснет ни на 7-ке, ни на 10-ке, ни на 11, на самом современном железе — скорее падает, но не виснет никогда.
Я догадываюсь, как специалисты улыбаются, когда видят мои потуги что-то сделать. Но (как сказал Уважаемый БигБаг) только так я смогу лучше понять работу 1С (или меньше непонимать).
Провожу новый эксперимент на копии.
Начал вчера вечером.
0) переместил бухитоги на 1 кв 2016
1) Нашел и пометил на удаление операции, которыми на 1.01.16 создавались остатки на 24-м счету. Это был 351 документ по 100 строк в каждом (в последнем — меньше).
2) удалил 1сбкттл (дбф и сдх) и 1сбкттлс (дбф и сдх).
3) сделал полный пересчет итогов через (вот не помню — через конфигуратор или через меню переноса бухитогов)
4) сделал оборотку за 1 кв 2016. Убедился, что стартовое сальдо на 24-м — нулевое.
5) последовательно, по 2 квартала, двигал бухгалтерские итоги к 1 кв 2022 (копия — по 12.02.22) и наблюдал, как растет 1сбкттл.
6) файл 1сбкттл вырос до уровня 1234689 кб (тогда как в стартовой копии — 1792890 кБ). Разница — 558201 кБ.
7) сделал ОСВ за период 1.01.16-31.03.22 в исходной копии и в пересчитанной копии. Разница составила только по счетам 24 и 00 (на одинаковую сумму с разным знаком). Судя по всему, на ОСВ повлияло только отключение вноса остатков по 24-му счету.
Все это заняло время с 20-00 до 24-00.
А потом мне захотелось запустить полный пересчет итогов через конфигуратор. Запустил. Периодически наблюдал за строкой состояния. Видел, что последний раз было сообщение про 1 кв 2010 года. Пошел спать.
Сейчас, утром, база продолжает считать. Но внизу висит сообщение «пересчет регистров». Посмотрел на 1сбкттл. Размер 1235053 кБ. (где-то подтянулось еще 400кБ). Последняя запись по файлам 1-С в 1:32 (ночи). То есть, с тех пор ни один файл не перезаписывался.
Предвижу замечание Уважаемого Злопчинского по поводу ранее леченных регистров.
Но у меня есть такое соображение:
Мне надо осознавать особенности того или иного пути решения. Для вас, профессионалов, они очевидны. Но не для меня. И то, что вы видите «между строк», — я не вижу. Вот я пытаюсь расширить свое понимание, чтобы осознаннее сделать выбор.
Сейчас я вижу, что ЛЮБОЕ решение по 24-29 счетам и файлу 1сбкттл не предполагает в будущем использования функции «полного пересчета итогов». Не смотря на то, что проводки по регистрам «вылечены» и не создают новых «хвостов», полный пересчет итогов все равно вернет «старые проблемы». Единственный вариант этого избежать — обрезать базу на 1.01.22. И уже в новой базе можно будет использовать полный пересчет итогов без риска «достать из архива» раздутые леченные регистры.
Я же сейчас параллельно своему эксперименту занимаюсь тем, что с программистом лечу проводки, связанные с 24 и 29 счетами (чтобы в новых документах не создавались новые субконто). То есть, я стараюсь не сводить все к временно-хирургическим решениям. Я стремлюсь сделать так, чтобы далее база работала правильно и в текущем режиме, и имела нормальное «прошлое», которое не будет всплывать при пересчете.
Еще раз — СПАСИБО ОГРОМНЕЙШЕЕ всем, кто помогает, и прямо или косвенно подталкивает к более качественному пониманию 1С, а значит и более качественному ее использованию.
(181) руководителю организации все-таки лучше бизнесом управлять, а не вдаваться в вопросы технического характера. как-то так. а то слон непроданным останется.. 😉
(182) удивительно, что у него вообще интерес к автоматизации деятельности остается, если посмотреть на расположение его по ай-пи.
Ну и не удививительно, что приходится самому вникать в процессы, т.к. поручать особо некому.
Кому он там непроданных слонов-то продает. Грустно это все, на самом деле.
И то, что база у него не с РФ расположением — в принципе, это видно по упоминаемым в обсуждении бух-счетам.
Задание 9
Что проверяется: Умение оценивать количественные характеристики процесса записи звука.
Краткие теоретические сведения: Поскольку данный тип задания является новым в КИМ ЕГЭ, приведем (пока без обоснования, обоснование ниже) математическую модель процесса звукозаписи:
N = k * F * L *T (1)
- • N – размер файла (в битах) , содержащего запись звука;
- • k — количество каналов записи (например, 1 – моно, 2 – стерео, 4 – квадро и т.д.);
- • F – частота дискретизации (в герцах), т.е. количество значений амплитуды звука фиксируемых за одну секунду;
- • L – разрешение, т.е. число бит, используемых для хранения каждого измеренного значения;
- • T – продолжительность звукового фрагмента (в секундах).
Как может выглядеть задание? Например, так: Заданы значения всех требуемых параметров процесса звукозаписи, кроме одного. Требуется оценить значение оставшегося параметра, например, размер файла или продолжительность звукового фрагмента.
Производится одноканальная (моно) звукозапись с частотой дискретизации 16 кГц и 24-битным разрешением. Запись длится 1 минуту, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
2. Пример задания
2.1. Условие задачи.
Производится одноканальная (моно) звукозапись с частотой дискретизации 16 кГц и 24-битным разрешением. Запись длится 1 минуту, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 0,2 Мбайт 2) 2 Мбайт 3) 3 Мбайт 4) 4 Мбайт
Приводим исходные данные к размерности биты-секунды-герцы и проводим расчеты по формуле (1):
Дано:
k = 1, т.к. одноканальная (моно) звукозапись;
F = 16 кГц = 16 000 Гц;
Найти N
Подставляем значение известных параметров в формулу (1)
= 2 4 *(2 3 *125)*(2 3 *3)* )*(2 2 * 15) = 2 12 *5625 (бит)=
= 2 12 *5625 бит = (2 12 *5625)/2 3 байт = 2 9 *5625 байт =
= (2 9 *5625)/ 2 20 Мбайт = 5625/2 11 Мбайт = 5625/2048 Мбайт.
Число 5625/2048 находится между числами 2 и 3. При этом оно ближе к 3, чем к 2, т.к. 3 * 2048 – 5625 < 1000; 5625 - 2 * 2048 > 1000.
Правильный вариант ответа: №3 (3 Мбайт)
Замечание. Другая идея решения приведена в п.3.3
3. Советы учителям и ученикам
3.1 Какие знания/умения/навыки нужны ученику, чтобы решить эту задачу
1) Не следует «зазубривать» формулу (1). Ученик, представляющий суть процесса цифровой звукозаписи, должен быть способен самостоятельно её сформулировать.
2) Необходимо умение записывать значения параметров в требуемой размерности, а также элементарные арифметические навыки, в т.ч. оперирование со степенями двойки.
3.2. Рекомендации для учителей: как разбирать задачу с учениками
Эти рекомендации – не догма, а попытка сделать выводы из собственного опыта. Ждем комментариев и Ваших рекомендаций.
А. Сильные ученики.
1. Скорее всего, они и так решат эту задачу.
2. Можно дать задание ученикам проверить формулу (1) на практике, записывая в файл звук с микрофона. При этом следует учесть, что она справедлива только в том случае, если записываемая информация не подвергается сжатию (формат WAV (PCM) без сжатия). Если используются аудиоформаты со сжатием (WMA, MP3), то объем получившегося файла будет по понятным причинам существенно меньше расчетного. Для экспериментов с цифровой звукозаписью можно использовать свободно распространяемый аудиоредактор Audacity (http://audacity.sourceforge.net/).
3. Целесообразно подчеркнуть концептуальную общность растрового представления звука и изображения, являющихся разновидностями одного и того же процесса приближенного представления непрерывного сигнала последовательность коротких дискретных сигналов, т.е. оцифровывания на основе дискретизации. В случае растрового изображения производится двумерная дискретизизация яркости в пространстве, в случае звука – одномерная дискретизация по времени. И в том, и в другом случае повышение частоты дискретизации (количества пикселей или звуковых отсчетов) и/или увеличение количества битов для представления одного отсчета (разрядность цвета или звука) ведет к повышению качества оцифровки, при одновременном росте размера файла с цифровым представлением. Отсюда – необходимость сжатия данных.
4. Желательно упомянуть об альтернативных способах оцифровки звука – запись «партий» инструментов в MIDI-формате. Здесь уместно провести аналогию с растровым и векторным представлением изображений.
Б. Не столь сильные ученики.
1. Необходимо обеспечить усвоение соотношения (1). Рекомендуется дать задания типа «Как изменится объем файла, если время записи звучания увеличить/уменьшить в p раз? »,
«Во сколько раз можно увеличить/уменьшить продолжительность записи, если максимальный размер файла увеличить/уменьшить в p раз? », «Как изменится объем файла, если количество бит для записи одного значения увеличить/уменьшить в p раз?» и т.д.
2. Необходимо убедиться, что учащиеся свободно оперируют размерностями, знают, что в Мбайте 2 23 бит и т.д.
3. Необходимо убедиться, что учащиеся достаточно арифметически грамотны, свободно владеют устным счетом со степенями двойки (умножение, деление, выделение сомножителей, представляющих собой 2 n ).
4. Придумывайте свои подходы и пробуйте их.
3.3. Полезный прием.
В подобных задачах часто возникают степени двойки. Перемножать и делить степени проще, чем произвольные числа: умножение и деление степеней сводится к сложению и вычитанию показателей.
Заметим, что числа 1000 и 1024 отличаются менее, чем на 3%, числа 60 и 64 отличаются менее, чем на 7%. Поэтому можно поступить так. Провести вычисления, заменив 1000 на 1024 = 2 10 и 60 на 64 = 2 6 , используя преимущества операций со степенями. Ближайший к полученному числу ответ и будет искомым. Можно после этого перепроверить себя, проведя точные вычисления. Но можно учесть, что общая погрешность вычислений при нашем приближении не превышает 10%. Действительно, 60*1000 = 60000; 64*1024=65536;
60000 > 0.9 * 65536 = 58982.4
Таким образом, правильный результат умножений по формуле (1) немного больше, чем 90% от полученного приближенного результата. Если учет погрешности не меняет результата – можно не сомневаться в ответе.
Пример. (ege.yandex.ru, вариант 1).
Производится двухканальная (стерео) звукозапись с частотой дискретизации 16 кГц и 32-битным разрешением. Запись длится 12 минут, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 30 Мбайт 2) 60 Мбайт 3) 75 Мбайт 4) 90 Мбайт
Решение. Размер записи в битах равен
С учетом замены 1000 на 1024=2 10 и 60 на 64=2 6 получим:
2 1 *2 4 *2 10 *2 5 *3*2 2 *2 6 =3*2 28
Как известно, 1 Мбайт = 2 20 байт = 2 23 бит. Поэтому 3*2 28 бит = 3*32 = 96 Мбайт. Уменьшив это число на 10%, получим 86.4 Мбайт. В обоих случаях ближайшей величиной является 90 Мбайт.
Правильный ответ: 4
3.4. Рекомендации для учеников: как решать подобные задачи
1. Прочитайте условие задачи. Выразите неизвестный параметр через известные. Особое внимание обратите, на размерность известных параметров. Она должна быть – биты-секунды-герцы (напомним, что 1 Гц = с -1 ). При необходимости, приведите значения параметров к нужной размерности, так же как это делается в задачах по физике.
2. Проводите вычисления, стараясь выделять степени двойки.
3. Обратите внимание, что в условии требуется выбрать наиболее подходящий ответ, поэтому высокая точность вычислений до знаков после запятой не требуется. Как только стало ясно, какой из вариантов ответов наиболее близок к вычисляемому значению, вычисления следует прекратить. Если расхождение со всеми вариантами ответов очень велико (в разы или на порядок), то вычисления надо перепроверить.
4. Задачи для самостоятельного решения
4.1. Клоны задачи 2012-А8-1.
Ниже приведены еще четыре варианта задачи 2012-А8-1.
А) Производится одноканальная (моно) звукозапись с частотой дискретизации 32 кГц и 24-битным разрешением. Запись длится 15 секунд, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 1,5 Мбайт 2) 3 Мбайт 3) 6 Мбайт 4) 12 Мбайт
Б) Производится двухканальная (стерео) звукозапись с частотой дискретизации 32 кГц и 24-битным разрешением. Запись длится 30 секунд, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 1,5 Мбайт 2) 3 Мбайт 3) 6 Мбайт 4) 12 Мбайт
В) Производится одноканальная (моно) звукозапись с частотой дискретизации 16 кГц и 32-битным разрешением. Запись длится 2 минуты, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 2 Мбайт 2) 4 Мбайт 3) 8 Мбайт 4) 16 Мбайт
Г) Производится одноканальная (моно) звукозапись с частотой дискретизации 16 кГц и 32-битным разрешением. Запись длится 4 минуты, ее результаты записываются в файл, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к размеру полученного файла?
1) 2 Мбайт 2) 4 Мбайт 3) 8 Мбайт 4) 16 Мбайт
4.2. Задача 2012-А8-2(обратная к предыдущей).
A) Производится одноканальная (моно) звукозапись с частотой дискретизации 16 кГц и 24-битным разрешением. Результаты записываются в файл, размер которого не может превышать 8 Мбайт, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к максимально возможной длительности записываемого звукового фрагмента?
1) 1минута 2) 30 секунд 3) 3 минуты 4) 90 секунд
Б) Производится двухканальная (стерео) звукозапись с частотой дискретизации 16 кГц и 24-битным разрешением. Результаты записываются в файл, размер которого не может превышать 8 Мбайт, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к максимально возможной длительности записываемого звукового фрагмента?
1) 1минута 2) 30 секунд 3) 3 минуты 4) 90 секунд
В) Производится одноканальная (моно) звукозапись с частотой дискретизации 48 кГц и 8-битным разрешением. Результаты записываются в файл, размер которого не может превышать 2,5 Мбайт, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к максимально возможной длительности записываемого звукового фрагмента?
1) 1минута 2) 30 секунд 3) 3 минуты 4) 90 секунд
Г) Производится одноканальная (моно) звукозапись с частотой дискретизации 48 кГц и 16-битным разрешением. Результаты записываются в файл, размер которого не может превышать 5 Мбайт, сжатие данных не производится. Какая из приведенных ниже величин наиболее близка к максимально возможной длительности записываемого звукового фрагмента?
1) 1минута 2) 30 секунд 3) 3 минуты 4) 90 секунд
5.Дополнение. Некоторые сведения о цифровой звукозаписи.
Распространение звука в воздухе можно рассматривать как распространение колебаний давления. Микрофон преобразует колебания давления в колебания электрического тока. Это аналоговый непрерывный сигнал. Звуковая плата обеспечивает дискретизацию входного сигнала от микрофона. Это делается следующим образом – непрерывный сигнал заменяется последовательностью измеренных с определенной точностью значений.
График аналогового сигнала:
Дискретное представление этого же сигнала (41 измеренное значение):
Дискретное представление этого же сигнала (161 измеренное значение, более высокая частота дискретизации):
Видно, что чем выше частота дискретизации, тем выше качество приближенного (дискретного) сигнала. Кроме частоты дискретизации, на качество оцифрованного сигнала влияет количество двоичных разрядов, отводимых для записи каждого значения сигнала. Чем больше бит отводится под каждое значение, тем более точно можно оцифровать сигнал.
Пример 2-х битного представления этого же сигнала (двумя разрядами можно пронумеровать только 4 возможных уровня величины сигнала):
Теперь можно выписать зависимость для размера файла с оцифрованным звуком
Учитывая возможность одновременной записи звука с нескольких микрофонов (стерео-, квадро- запись и т.д.), что делается для усиления реалистичности при воспроизведении, получаем формулу (1).
При воспроизведении звука цифровые значения преобразуются в аналоговые. Электрические колебания, передаваемые на динамики, преобразуются ими снова в колебания давления воздуха.
0 Comments
Оставьте коммент первым.
Вопросы и ответы для собеседования Go-разработчика
Структурирование информации — очень полезный навык. И дабы привнести некоторый порядок в этап подготовки к интервью на должность Golang разработчика (и немножко техлида) решил записывать в этой заметке в формате FAQ те вопросы, которые я задавал, задавали мне или просто были мной найдены на просторах сети вместе с ответами на них. Стоит относиться к ним как к шпаргалке (если затупишь на реальном интервью — будет где подсмотреть) и просто набору тем, которым тебе стоит уделить внимание.
Я постарался копнуть в каждый вопрос чуть глубже чем, возможно, надо бы — что бы у читателя был не только короткий ответ на вопрос, но и некоторое понимание «а почему именно так устроена та или иная штука». Более того, крайне рекомендую ознакомиться и с ссылками на источники, что будут под ответами — там вы найдете более развернутые ответы.
Да, это очень объемный пост, и вряд ли его можно вдумчиво осилить за один подход, но поместив его в закладки он, возможно, когда-то сослужит вам добрую службу (читать его можно по частям, находясь в метро или между вечными совещаниями; да и Ctrl + F никто не отменял). Ещё ему очень не хватает оглавления для удобной навигации между вопросами, но у хабраредактора нет возможности генерировать TOC (если будут запросы об этом в комментариях — сделаю его руками). Об очепятках, пожалуйста, пишите в личку.
Расскажи о себе?
Чаще всего этот вопрос идёт первым и даёт возможность интервьюверу задать вопросы связанные с твоим резюме, познакомиться с тобой, попытаться понять твой характер для построения последующих вопросов. Следует иметь в виду, что интервьюверу не всегда удается подготовиться к интервью, или он банально не имеет перед глазами твоего резюме. Тут есть смысл ещё раз представиться (часто в мессенджерах используются никнеймы, а твоё реальное имя он мог забыть), назвать свой возраст, образование, рассказать о предыдущих местах работы и должностях, сколько лет в индустрии, какие ЯП и технологии использовал — только «по верхам», для того чтоб твой собеседник просто понял с кем он «имеет дело».
Расскажи о своем самом интересном проекте?
К этому вопросу есть смысл подготовиться заранее и не спустя рукава. Дело в том, что это тот момент, когда тебе надо подобно павлину распустить хвост и создать правильное первое впечатление о себе, так как этот вопрос тоже очень часто идёт впереди всех остальных. Возьми и выпиши для себя где-нибудь на листочке основные тезисы о том, что это был за проект/сервис/задача, уделяя основное внимание тому какой профит это принесло для компании/команды в целом. Например:
- Я со своей командой гоферов из N человек в течении трех месяцев создали аналог сервиса у которого компания покупала данные за $4000 в месяц, а после перехода на наш сервис — расходы сократились до $1500 в месяц и значительно повысилось их качество и uptime;
- Внедренные мной практики в CI/CD пайплайны позволили сократить время на ревью изменений в проектах на 25..40%, а зная сколько стоит время работы разработчиков — вы сами всё понимаете;
- Разработанный мной сервис состоял из такого-то набора микросервисов, такие-то службы и протоколы использовал, были такие-то ключевые проблемы которые мы так-то зарешали; основной ценностью было то-то.
Кем был создан язык, какие его особенности?
Go (часто также golang) — компилируемый многопоточный язык программирования, разработанный внутри компании Google. Разработка началась в 2007 году, его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Официально язык был представлен в ноябре 2009 года.
В качестве ключевых особенностей можно выделить:
- Простая грамматика (минимум ключевых слов — язык создавался по принципу «что ещё можно выкинуть» вместо «что бы ещё в него добавить»)
- Строгая типизация и отказ от иерархии типов (но с сохранением объектно-ориентированных возможностей)
- Сборка мусора (GC)
- Простые и эффективные средства для распараллеливания вычислений
- Чёткое разделение интерфейса и реализации
- Наличие системы пакетов и возможность импортирования внешних зависимостей (пакетов)
- Богатый тулинг «из коробки» (бенчмарки, тесты, генерация кода и документации), быстрая компиляция
Для того, чтоб вспомнить историю создания Go и о его особенностях можно посмотреть:
Go — императивный или декларативный? А в чем разница?
Go является императивным языком.
Императивное программирование — это описание того, как ты делаешь что-то (т.е. конкретно описываем необходимые действия для достижения определенного результата), а декларативное — того, что ты делаешь (например, декларативным ЯП является SQL — мы описываем что мы хотим получить от СУБД, но не описываем как именно она должна это сделать).
Что такое ООП? Как это сделано в Golang?
ООП это методология (подход) программирования, основанная на том, что программа представляет собой некоторую совокупность объектов-классов, которые образуют иерархию наследования. Ключевые фишки — минимализация повторяемости кода (принцип DRY) и удобство понимания/управления. Фундаментом ООП можно считать идею описания объектов в программировании подобно объектам из реального мира — у них есть свойства, поведение, они могут взаимодействовать. Мы (люди) так понимаем мир, и нам (людям) так проще описывать всякие штуки в коде. Основные принципы в ООП:
- Абстракция вообще присуща для любого программирования, а не только для ООП. По большому счету (топорный, но понятный пример) это про выделение общего и объединение этого в какие-то сущности но без реализации, про контракты. Например — экземпляры абстрактных классов не могут быть созданы ( new AbstractClass ), но могут содержать абстрактные методы, чтоб разработчик решив наследоваться от этого абстрактного класса их реализовал так, как ему нужно для своих целей (например — ходить в SQL СУБД или файл). Другой пример — это интерфейсы, они же контракты чистой воды — содержат только сигнатуры методов и ни капельки реализации. Но абстракция не ограничивается ими и должна быть умеренной, так как усложняет архитектуру приложения в общем и целом. Опираться следует на интуицию и опыт. Слишком много слоев абстракции (ещё раз — тут дело не ограничивается интерфейсами и абстрактными классами) приводит к переусложнению и головной боли последующего сопровождения продукта. Недостаточная — к сложности внесения изменений и расширению функционала
- Инкапсуляция про контроль доступа к свойствам объекта и их динамическая валидация/преобразования. Если метод/свойство должно быть доступно «извне» объекта — объявляем публичным, иначе — приватным. Если есть необходимость переопределять его из потомков класса — то защищенным (protected). Python, например, реализуют инкапсуляцию, но не предусматривает возможности сокрытия в принципе; в то время как С++ и Java она просто всюду
- Наследование это возможность (барабанная дробь!) наследоваться одним объектам от других, «перенимая» все методы родительских объектов. Своеобразный вариант Матрешки. Т.е. выделяя в родительских объектах «всё общее» мы можем не повторяться в реализации частных, а просто «наследоваться»
- Полиморфизм — «поли» — много, «морф» — вид. Везде, где есть интерфейсы — подразумевается полиморфизм. Суть — это контракты (интерфейсы), мы можем объявить «что-то умеет закрывать себя методом Close() «, и нам не важно что именно это будет. Реализаций может быть много, и если это что-то умеет делать то, что нам надо — нам удобнее с этим работать
Тут же можно упомянуть про знание SOLID, а именно:
- S (single responsibility principle, принцип единственной ответственности) — определенный класс/модуль должен решать только определенную задачу, максимально узко но максимально хорошо (своеобразные UNIX-way). Если для выполнения своей задачи ему требуются какие-то другие ресурсы — они в него должны быть инкапсулированы (это отсылка к принципу инверсии зависимостей)
- O (open-closed principle, принцип открытости/закрытости) — классы/модули должны быть открыты для расширения, но закрыты для модификации. Должна быть возможность расширить поведение, наделить новым функционалом, но при этом исходный код/логика модуля должна быть неизменной
- L (Liskov substitution principle, принцип подстановки Лисков) — поведение наследующих классов не должно противоречить поведению, заданному базовым классом, то есть поведение наследующих классов должно быть ожидаемым для кода
- I (interface segregation principle, принцип разделения интерфейса) — много тонких интерфейсов лучше, чем один толстый
- D (dependency inversion principle, принцип инверсии зависимостей) — «завязываться» на абстракциях (интерфейсах), а не конкретных реализациях. Так же (это уже про IoC, но всё же) можно рассказать что если какому-то классу для своей работы требуется функциональность другого — то есть смысл «запрашивать» её в конструкторе нашего класса используя интерфейс, под который подходит наша зависимость. Таким образом целевая реализация опирается только на интерфейсы (не зависит от реализаций) и соответствует принципу под буквой S
А теперь о том, как это реализовано в Go (наконец-то!).
В Go нет классов, объектов, исключений и шаблонов. Нет иерархии типов, но есть сами типы (т.е. возможность описывать свои типы/структуры). Структурные типы (с методами) служат тем же целям, что и классы в других языках. Так же следует упомянуть что структура определяет состояние.
В Go нет наследования. Совсем. Но есть встраивание (называемое «анонимным», так как Foo в Bar встраивается не под каким-то именем, а без него) при этом встраиваются и свойства, и функции:
import "fmt" type Foo struct < name string Surname string >func (f Foo) SayName() string < return f.name >type Bar struct < Foo >func main() < bar := Bar> fmt.Println(bar.SayName()) // one fmt.Println(bar.Surname) // baz bar.name = "two" fmt.Println(bar.SayName()) // two >
Есть интерфейсы (это типы, которые объявляют наборы методов). Подобно интерфейсам в других языках, они не имеют реализации. Объекты, которые реализуют все методы интерфейса, автоматически реализуют интерфейс (так называемый Duck-typing). Не существует наследования или подклассов или ключевого слова Implements :
import "fmt" type Speaker interface < Speak() string >type Foo struct<> func (Foo) Speak() string < return "foo" >type Bar struct<> func (Bar) Speak() string < return "bar" >func main() < var foo, bar Speaker = new(Foo), &Bar<>fmt.Println(foo.Speak()) // foo fmt.Println(bar.Speak()) // bar >
В примере выше мы объявили переменные foo и bar с явным указанием интерфейсного типа, а так интерфейс это «ссылочный» тип (на самом деле в Go нет ссылок, но есть указатели) — то и структуры мы инициализировали указателями на них с использованием new() (что аллоцирует структуру и возвращает указатель на неё) и (или) & .
Инкапсуляция реализована на уровне пакетов. Имена, начинающиеся со строчной буквы, видны только внутри этого пакета (не являются экспортируемыми). И наоборот — всё, что начинается с заглавной буквы — доступно извне пакета. Дешево и сердито.
Полиморфизм — это основа объектно-ориентированного программирования: способность обрабатывать объекты разных типов одинаково, если они придерживаются одного и того же интерфейса. Интерфейсы Go предоставляют эту возможность очень прямым и интуитивно понятным способом. Пример использования интерфайса был описан выше.
Что можно почитать: ООП в картинках, Golang и ООП
Как устроено инвертирование зависимостей?
Инвертирование зависимостей позволяет в нашем коде не «завязываться» на конкретную реализацию (используя, например, интерфейсы), тем самым понижая связанность кода и повышая его тестируемость. Так же сужается зона ответственности конечной структуры/пакета, что повышает его переиспользуемость.
Принцип инверсии зависимостей (dependency inversion principle) в Go который можно реализовывать следующим образом:
import ( "errors" "fmt" ) type speaker interface < Speak() string >type Foo struct < s speaker // s *Foo - было бы плохо >func NewFoo(s speaker) (*Foo, error) < if s == nil < return nil, errors.New("speaker is nil") >return &Foo, nil > func (f Foo) SaySomething() string < return f.s.Speak() >func main() < var foo, err = NewFoo(someSpeaker) if err != nil < panic(err) >fmt.Println(foo.SaySomething()) // depends on the speaker implementation >
Мы объявляем интерфейс speaker не экспортируемым на нашей, принимающей стороне, и используя псевдо-конструктор NewFoo гарантируем что свойство s будет проинициализировано верным типом (дополнительно проверяя его на nil ).
Как сделать свои методы для стороннего пакета?
Например, если мы используем логгер Zap в нашем проекте, и хотим к этому Zap-у прикрутить наши методы — то для этого нам нужно будет создать свою структуру, внутри в неё встраивать логгер Zap-а, и к этой структуре уже прикручивать требуемые методы. Просто «навесить сверху» функции на сторонний пакет мы не можем.
Типы данных и синтаксис
К фундаментальным типам данных можно отнести:
- Целочисленные — int , int , uint , uint , byte как синоним uint8 и rune как синоним int32 . Типы int и uint имеют наиболее эффективный размер для определенной платформы (32 или 64 бита), причем различные компиляторы могут предоставлять различный размер для этих типов даже для одной и той же платформы
- Числа с плавающей запятой — float32 (занимает 4 байта/32 бита) и float64 (занимает 8 байт/64 бита)
- Комплексные числа — complex64 (вещественная и мнимая части представляют числа float32 ) и complex128 (вещественная и мнимая части представляют числа float64 )
- Логические aka bool
- Строки string
Как устроены строки в Go?
В Go строка в действительности является слайсом (срезом) байт, доступным только для чтения. Строка содержит произвольные байты, и у неё нет ёмкости ( cap ). При преобразовании слайса байт в строку ( str := string(slice) ) или обратно ( slice := []byte(str) ) — происходит копирование массива (со всеми следствиями).
Создание подстрок работает очень эффективно. Поскольку строка предназначена только для чтения, исходная строка и строка, полученная в результате операции среза, могут безопасно совместно использовать один и тот же массив:
var ( str = "hello world" sub = str[0:5] usr = "/usr/kot"[5:] ) print(sub, " ", usr) // hello kot
Go использует тип rune (алиас int32 ) для представления Unicode. Конструкция for . range итерирует строку посимвольно (а не побайтово, как можно было бы предположить):
var str = "привет" println(str, len(str)) // привет 12 for i, c := range str < println(i, c, string(c)) >// 0 1087 п // 2 1088 р // 4 1080 и // 6 1074 в // 8 1077 е // 10 1090 т
И мы видим, что для кодирования каждого символа кириллицы используются по 2 байта.
Эффективным способом работы со строками (когда есть необходимость часто выполнять конкатенацию, например) является использование слайса байт или strings.Builder :
import "strings" func main() < // происходит только 1 аллокация при вызове `Grow()` var str strings.Builder str.Grow(12) // сразу выделяем память str.WriteString("hello") str.WriteRune(' ') str.WriteString("мир") println(str.String()) // hello мир >
И ещё одну важную особенность стоит иметь в виду — это подсчет длины строки (например — для какой-нибудь валидации). Если считать по количеству байт, и строка содержит не только ASCII символы — то количество байт и фактическое количество символов будут расходиться:
const str = "hello мир!" println(len(str), utf8.RuneCountInString(str)) // 13 10
Тут дело в том, что для кодирования символов м , и и р используются 2 байта вместо одного. Поэтому len == 13 , а фактически в строке лишь 10 символов (пакет utf8 , к примеру, нам в помощь).
Что можно почитать: Строка, байт, руна, символ в Golang
В чём ключевое отличие слайса (среза) от массива?
- Срез — всегда указатель на массив, массив — значение
- Срез может менять свой размер и динамически аллоцировать память
В Go не бывает ссылок — но есть указатели. Где говорится про «по ссылке» имеется в виду «по указателю»
Слайсы и массивы в Go это упорядоченные структуры данных последовательностей элементов. Ёмкость массива объявляется в момент его создания, и после изменить её уже нельзя (его длина это часть его типа). Память, необходимая для хранения элементов массива выделяется соответственно сразу при его объявлении, и по умолчанию инициализируется в соответствии с нулевыми значением для типа ( fasle для bool , 0 для int , nil для интерфейсов и т.д.). На стеке можно разместить массив объемом 10 MB. В качестве размера можно использовать константы (компилятор должен знать это значение на этапе компиляции, т.е. что-то вида var a [getSize()]int или i := 3; var a [i]int недопустимо):
const mySize uint8 = 8 type myArray [mySize]byte var constSized = [. ]int // размер сам посчитается исходя из кол-ва эл-ов
Кстати, массивы с элементами одного типа но с разными размерами являются разными типами. Массивы не нужно инициализировать явно; нулевой массив — это готовый к использованию массив, элементы которого являются нулями:
var a [4]int // [0 0 0 0] a[0] = 1 // [1 0 0 0] i := a[0] // i == 1
Представление [4]int в памяти — это просто четыре целых значения, расположенных последовательно. Так же следует помнить что в Go массивы передаются по значению, т.е. передавая массив в какую-либо функцию она получает копию массива (для передачи его указателя нужно явно это указывать, т.е. foo(&a) ).
А слайс же это своего рода версия массива но с вариативным размером (структура данных, которая строится поверх массива и предоставляет доступ к элементами базового массива). Слайсы до 64 KB могут быть размещены на стеке. Если посмотреть исходники Go (src/runtime/slice.go), то увидим:
type slice struct < array unsafe.Pointer // указатель на массив len int // длина (length) cap int // вместимость (capacity) >
Для аллокации слайса можно воспользоваться одной из команд ниже:
var ( a = []int<> // [] len=0 cap=0 b = []int // [1 2] len=2 cap=2 c = []int // [0 0 0 0 0 123] len=6 cap=6 d = make([]int, 5, 10) // [0 0 0 0 0] len=5 cap=10 )
В последнем случае рантайм Go создаст массив из 10 элементов (выделит память и заполнит их нулями) но доступны прямо сейчас нам будут только 5, и установит значения len в 5 , а cap в 10 . Cap означает ёмкость и помогает зарезервировать место в памяти на будущее, чтобы избежать лишних операций выделения памяти при росте слайса (это ключевой параметр для аллокации памяти, влияет на производительность вставки в срез). При добавлении новых элементов в слайс новый массив для него не будет создаваться до тех пор, пока cap меньше len .
Слайсы передаются «по ссылке» (фактически будет передана копия структуры slice со своими len и cap , но указатель на массив array будет тот-же самый). Для защиты слайса от изменений следует передавать его копию:
var ( a = []int b = make([]int, len(a)) ) copy(b, a) fmt.Println(a, b) // [1 2 0 0 1] [1 2 0 0 1]
Важной особенностью является то, так как «под капотом» у слайса лежит указатель на массив — при изменении значений слайса они будут изменяться везде, где слайс используется (будь то присвоение в переменную, передача в функцию и т.д.) до момента, пока размер слайса не будет переполнен и не будет выделен новый массив для его значений (т.е. в момент изменения cap слайса всегда происходит копирование данных массива):
var ( one = []int // [1 2] two = one // [1 2] ) two[0] = 123 fmt.Println(one, two) // [123 2] [123 2] one = append(one, 666) fmt.Println(one, two) // [123 2 666] [123 2]
Как вы отсортируете массив структур по алфавиту по полю Name ?
Например, преобразую массив в слайс и воспользуюсь функцией sort.SliceStable :
package main import ( "fmt" "sort" ) func main() < var arr = [. ]struct< Name string >, , > // ^^^^^^^^^^^^^^^^^^^^^ анонимная структура с нужным нам полем fmt.Println(arr) // [ ] sort.SliceStable(arr[:], func(i, j int) bool < return arr[i].Name < arr[j].Name >) // ^^^ вот тут вся "магия" - из массива сделали слайс fmt.Println(arr) // [ ] >
Вся магия в том, что при создании слайса из массива «под капотом» у слайса начинает лежать исходный массив, и функции из пакета sort нам становятся доступны над ними. Т.е. изменяя порядок элементов в слайсе функцией sort.SliceStable мы будем менять их в нашем исходном массиве.
Как работает append в слайсе?
append() делает простую операцию — добавляет элементы в слайс и возвращает новый. Но под капотом там делаются довольно сложные манипуляции, чтобы выделять память только при необходимости и делать это эффективно.
Сперва append сравнивает значения len и cap у слайса. Если len меньше чем cap , то значение len увеличивается, а само добавляемое значение помещается в конец слайса. В противном случае происходит выделение памяти под новый массив для элементов слайса, в него копируются значения из старого, и значение помещается уже в новый массив.
Увеличении размера слайса (метод growslice ) происходит по следующему алгоритму — если его размер менее 1024 элементов, то его размер будет увеличиваться вдвое; иначе же слайс увеличивается на ~12.5% от своего текущего размера.
Что важно помнить — если на основании слайса one выделить подслайс two , а затем увеличим слайс one (и его вместимость будет превышена) — то one и two будут уже ссылаться на разные участки памяти!
var ( one = make([]int, 4) // [0 0 0 0] two = one[1:3] // [0 0] ) one[2] = 11 fmt.Println(one, two) // [0 0 11 0] [0 11] fmt.Printf("%p %p\n", one, two) // 0xc0000161c0 0xc0000161c8 one = append(one, 1) fmt.Printf("%p %p\n", one, two) // 0xc00001c1c0 0xc0000161c8 one[2] = 22 fmt.Println(one, two) // [0 0 22 0 1] [0 11] fmt.Printf("%p %p\n", one, two) // 0xc00001c1c0 0xc0000161c8
Есть еще много примеров добавления, копирования и других способов использования слайсов тут — Slice Tricks.
Что можно почитать: Как не наступать на грабли в Go
Задача про слайсы #1
Вопрос: У нас есть 2 функции — одна делает append() чего-то в слайс, а другая просто сортирует слайс, используя пакет sort . Модифицируют ли слайс первая и (или) вторая функции?
Ответ: append() не модифицирует а возвращает новый слайс, а sort модифицирует порядок элементов, если он изначально был не отсортирован.
Задача про слайсы #2
Вопрос: Что выведет следующая программа?
package main import "fmt" func main() < a := [5]intt := a[3:4:4] fmt.Println(t[0]) >
Объяснение: Такой синтаксис позволяет задать capacity (вместимость) для полученного под-слайса, который будет равен «последний элемент минус первый элемент из выражения в квадратных скобках», т.е. из примера выше он будет равен 1 (т.к. от четырёх, т.е. третьего сегмента вычитаем первый, т.е. тройку). Если бы выражение имело вид a[3:4:5] , то cap была бы равна 2 (5 — 3 = 2). Но при этом на сами данные он не влияет.
Появилась эта штука в Go 1.2.
Что можно почитать: Slicing a slice with slice [a : b : c] , Full slice expressions
Какое у слайса zero value? Какие операции над ним возможны?
Zero value у слайса всегда nil , а len и cap равны нулю, так как «под ним» нет инициализированного массива:
var a []int println(a == nil, len(a), cap(a)) // true 0 0 a = append(a, 1) println(a == nil, len(a), cap(a)) // false 1 1
Как видно из примера выше — несмотря на то, что a == nil (слайс «не инициализирован»), с этим слайсом возможна операция append — в этом случае Go самостоятельно создаёт нижележащий массив и всё работает так, как и ожидается. Более того — для полной очистки слайса рекомендуется его присваивать к nil .
Так же важно помнить, что не делая make для слайса — не получится сделать пре-аллокацию, что часто очень болезненно для производительности.
Что можешь рассказать про map ?
Карта ( map или hashmap ) — это неупорядоченная коллекция пар вида ключ-значение. Пример:
type myMap map[string]int
Подобно массивам и слайсам, к элементам мапы можно обратиться с помощью скобок:
var m = make(map[string]int) // инициализация m["one"] = 1 // запись в мапу fmt.Println(m["one"], m["two"]) // 1 0
Лучше выделить память заранее (передавая вторым аргументом функции make ), если известно количество элементов — избежим эвакуаций
В случае с m[«two»] вернулся 0 так как это является нулевым значением для типа int . Для проверки существования ключа используем конструкцию вида (доступ к элементу карты может вернуть два значения вместо одного) называемую «multiple assignment»:
var m = map[string]int v1, ok1 := m["one"] // чтение v2, ok2 := m["two"] fmt.Println(v1, ok1) // 1 true fmt.Println(v2, ok2) // 0 false for k, v := range m < // итерация всех эл-ов мапы fmt.Println(k, v) >delete(m, "one") // удаление v1, ok1 = m["one"] fmt.Println(v1, ok1) // 0 false
Мапы всегда передаются по ссылке (вообще-то Go не бывает ссылок, невозможно создать 2 переменные с 1 адресом, как в С++ например; но зато можно создать 2 переменные, указывающие на один адрес — но это уже указатели). Если же быть точнее, то мапа в Go — это просто указатель на структуру hmap :
type hmap struct < // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields >
Так же структура hmap содержит в себе следующее:
- Количество элементов
- Количество «ведер» (представлено в виде логарифма для ускорения вычислений)
- Seed для рандомизации хэшей (чтобы было сложнее заddosить — попытаться подобрать ключи так, что будут сплошные коллизии)
- Всякие служебные поля и главное указатель на buckets, где хранятся значения
На картинке схематичное изображение структуры в памяти — есть хэдер hmap, указатель на который и есть map в Go (именно он создается при объявлении с помощью var, но не инициализируется, из-за чего падает программа при попытке вставки). Поле buckets — хранилище пар ключ-значение, таких «ведер» несколько, в каждом лежит 8 пар. Сначала в «ведре» лежат слоты для дополнительных битов хэшей ( e0..e7 названо e — потому что extra hash bits). Далее лежат ключи и значения как сначала список всех ключей, потом список всех значений.
По хэш функции определяется в какое «ведро» мы кладем значение, внутри каждого «ведра» может лежать до 8 коллизий, в конце каждого «ведра» есть указатель на дополнительное, если вдруг предыдущее переполнилось.
Как растет map ?
В исходном коде можно найти строчку Maximum average load of a bucket that triggers growth is 6.5 . То есть, если в каждом «ведре» в среднем более 6,5 элементов, происходит увеличение массива buckets . При этом выделяется массив в 2 раза больше, а старые данные копируются в него маленькими порциями каждые вставку или удаление, чтобы не создавать очень крупные задержки. Поэтому все операции будут чуть медленнее в процессе эвакуации данных (при поиске тоже, нам же приходится искать в двух местах). После успешной эвакуации начинают использоваться новые данные.
Из-за эвакуации данных нельзя и взять адрес мапы — представьте, что мы взяли адрес значения, а потом мапа выросла, выделилась новая память, данные эвакуировались, старые удалились, указатель стал неправильным, поэтому такие операции запрещены.
Что там про поиск?
Поиск, если разобраться, устроен не так уж и сложно: проходимся по цепочкам «ведер», переходя в следующее, если в этом не нашли. Поиск в «ведре» начинается с быстрого сравнения дополнительного хэша, для которого используется всего 8 бит (вот для чего эти e0. e7 в начале каждого — это «мини» хэш пары для быстрого сравнения). Если не совпало, идем дальше, если совпало, то проверяем тщательнее — определяем где лежит в памяти ключ, подозреваемый как искомый, сравниваем равен ли он тому, что запросили. Если равен, определяем положение значения в памяти и возвращаем.
К сожалению, мир не совершенен. Когда имя хешируется, то некоторые данные теряются, так как хеш, как правило, короче исходной строки. Таким образом, в любой реализации хеш таблицы неизбежны коллизии когда по двум ключам получаются одинаковые хеши. Как следствие, поиск может быть дороже чем O(1) (возможно это связано с кешем процессора и коллизиями коротких хэшей), так что иногда выгоднее использовать бинарный поиск по слайсу данных нежели чем поиск в мапе (пишите бенчмарки).
Что можно почитать: Хэш таблицы в Go. Детали реализации, Кажется, поиск в map дороже чем O(1)
Есть ли у map такие же методы как у слайса: len , cap ?
У мапы есть len но нет cap . У нас есть только overflow который указывает «куда-то» когда мапа переполняется, и поэтому у нас не может быть capacity .
Какие типы ключей разрешены для ключа в map ?
Любым сравнимым (comparable) типом, т.е. булевы, числовые, строковые, указатели, канальные и интерфейсные типы, а также структуры или массивы, содержащие только эти типы. Слайсы, мапы и функции использовать нельзя, так как эти типы не сравнить с помощью оператора == или != .
Может ли ключом быть структура? Если может, то всегда ли?
Как было сказано выше — структура может быть ключом до тех пор, пока мы в поля структуры не поместим какой-либо слайс, мапу или любой другой non-comparable тип данных (например — функцию).
Что будет в map , если не делать make или short assign ?
Будет паника (например — при попытке что-нибудь в неё поместить), так как любые «структурные» типы (а мапа как мы знаем таковой является) должны быть инициализированы для работы с ними.
Race condition. Потокобезопасна ли мапа?
Нет, потокобезопасной является sync.Map . Для обеспечения безопасности вокруг мапы обычно строится структура вида:
type ProtectedIntMap struct < mx sync.RWMutex m map[string]int >func (m *ProtectedIntMap) Load(key string) (val int, ok bool) < m.mx.RLock() val, ok = m.m[key] m.mx.RUnlock() return >func (m *ProtectedIntMap) Store(key string, value int)
Что такое интерфейс?
Интерфейсы — это инструменты для определения наборов действий и поведения. Интерфейсы — это в первую очередь контракты. Они позволяют объектам опираться на абстракции, а не фактические реализации других объектов. При этом для компоновки различных поведений можно группировать несколько интерфейсов. В общем смысле — это набор методов, представляющих стандартное поведение для различных типов данных.
Как устроен Duck-typing в Go?
Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка и есть.
Если структура содержит в себе все методы, что объявлены в интерфейсе, и их сигнатуры совпадают — она автоматически удовлетворяет интерфейс.
Такой подход позволяет полиморфно (полиморфизм — способность функции обрабатывать данные разных типов) работать с объектами, которые не связаны в иерархии наследования. Достаточно, чтобы все эти объекты поддерживали необходимый набор методов.
Интерфейсный тип
В Go интерфейсный тип выглядит вот так:
type iface struct
Где tab — это указатель на Interface Table или itable — структуру, которая хранит некоторые метаданные о типе и список методов, используемых для удовлетворения интерфейса, а data указывает на реальную область памяти, в которой лежат данные изначального объекта (статическим типом).
Компилятор генерирует метаданные для каждого статического типа, в которых, помимо прочего, хранится список методов, реализованных для данного типа. Аналогично генерируются метаданные со списком методов для каждого интерфейса. Теперь, во время исполнения программы, runtime Go может вычислить itable на лету (late binding) для каждой конкретной пары. Этот itable кешируется, поэтому просчёт происходит только один раз.
Зная это, становится очевидно, почему Go ловит несоответствия типов на этапе компиляции, но кастинг к интерфейсу — во время исполнения.
Что важно помнить — переменная интерфейсного типа может принимать nil . Но так как объект интерфейса в Go содержит два поля: tab и data — по правилам Go, интерфейс может быть равен nil только если оба этих поля не определены (faq):
var ( builder *strings.Builder stringer fmt.Stringer ) fmt.Println(builder, stringer) // nil nil fmt.Println(stringer == nil) // true fmt.Println(builder == nil) // true stringer = builder fmt.Println(builder, stringer) // nil nil fmt.Println(stringer == nil) // false (. ) fmt.Println(builder == nil) // true
Пустой interface<>
Ему удовлетворяет вообще любой тип. Пустой интерфейс ничего не означает, никакой абстракции. Поэтому использовать пустые интерфейсы нужно в самых крайних случаях.
На какой стороне описывать интерфейс — на передающей или принимающей?
Многое зависит от конкретного случая, но по умолчанию описывать интерфейсы следует на принимающей стороне — таким образом, ваш код будет меньше зависеть от какого-то другого кода/пакета/реализации.
Другими словами, если нам в каком-то месте требуется «что-то что умеет себя закрывать», или — умеет метод Close() error , или (другими словами) удовлетворят интерфейсу:
type something interface
То он (интерфейс) должен быть описан на принимающей стороне. Так принимающая сторона не будет ничего знать о том, что именно в неё может «прилететь», но точно знает поведение этого «чего-то». Таким образом реализуется инверсия зависимости, и код становится проще переиспользовать/тестировать.
Что такое замыкание?
Замыкания — это такие функции, которые вы можете создавать в рантайме и им будет доступно текущее окружение, в рамках которого они были созданы.
Функции, у которых есть имя — это именованные функции. Функции, которые могут быть созданы без указания имени — это анонимные функции.
func main() < var text = "some string" var ourFunc = func() < // именованное замыкание println(text) >ourFunc() // some string getFunc()() // another string > func getFunc() func() < return func() < // анонимное println("another string") >>
Замыкания сохраняют состояние. Это означает, что состояние переменных содержится в замыкании в момент декларации. Одна из самых очевидных ловушек — это создание замыканий в цикле:
var funcs = make([]func(), 0, 5) for i := 0; i < 5; i++ < funcs = append(funcs, func() < println("counter =", i) >) // исправляется так: //var value = i //funcs = append(funcs, func() < println("counter https://kodazm.ru/articles/go/zamykaniya/" rel="nofollow noopener noreferrer">Замыкания
Что такое сериализация? Зачем она нужна?
Сериализация — это процесс преобразования объекта в поток байтов для сохранения или передачи. Обратной операцией является десериализация (т.е. восстановление объекта/структуры из последовательности байтов). Синонимом можно считать термин "маршалинг" (от англ. marshal
— упорядочивать).
Из минусов сериализации можно выделить нарушение инкапсуляции, т.е. после сериализации "приватные" свойства структур могут быть доступны для изменения.
Типичными примерами сериализации в Go являются преобразование структур в json-объекты. Кроме json существуют различные кодеки типа MessagePack
, CBOR
и т.д.
Что такое type switch
?
Проверка типа переменной, а не её значения. Может быть в виде одного switch
и множеством case
:
package main func checkType(i interface<>) < switch i.(type) < case int: println("is integer") case string: println("is string") default: println("has unknown type") >>
А может в виде if -конструкции:
package main func main() < var any interface<>any = "foobar" if s, ok := any.(string); ok < println("this is a string:", s) >// а так можно проверить наличие функций у структуры if closable, ok := any.(interface< Close() >); ok < closable.Close() >>
Какие битовые операции знаешь?
Побитовые операторы проводят операции непосредственно на битах числа.
// Побитовое И/AND (разряд результата равен 1 только тогда, когда оба соответствующих бита операндов равны 1) println(0b111_000 /* 56 */ & 0b011_110 /* 30 */ == 0b011_000 /* 24 */) // Побитовое ИЛИ/OR (разряд результата равен 0 только тогда, когда оба соответствующих бита в равны 0) println(0b111_000 /* 56 */ | 0b011_110 /* 30 */ == 0b111_110 /* 62 */) // Исключающее ИЛИ/XOR (разряд результата равен 1 только тогда, когда только один бит равен 1) println(0b111_000 /* 56 */ ^ 0b011_110 /* 30 */ == 0b100_110 /* 38 */) // Сброс бита AND NOT println(0b111_001 /* 57 */ &^ 0b011_110 /* 30 */ == 0b100_001 /* 33 */) // Сдвиг бита влево println(0b000_001 /* 1 */ > 1 == 0b000_011 /* 3 */)
Пример использования простой битовой маски:
type Bits uint8 const ( F0 Bits = 1 func Clear(b, flag Bits) Bits < return b &^ flag >func Toggle(b, flag Bits) Bits < return b ^ flag >func Has(b, flag Bits) bool < return b&flag != 0 >func main() < var b Bits b = Set(b, F0) b = Toggle(b, F2) for i, flag := range [. ]Bits < println(i, Has(b, flag)) >// 0 true // 1 false // 2 true >
Что можно почитать: О битовых операциях, Поразрядные операции
Дополнительный блок фигурных скобок в функции
Его можно использовать, и он означает отдельный скоуп для всех переменных, объявленных в нём (возможен и "захват переменных" объявленных вне скоупа ранее, естественно). Иногда используется для декомпозиции какого-то отдельного куска функции, к примеру.
var i, s1 = 1, "foo" < var j, s2 = 2, "bar" println(i, s1) // 1 foo println(j, s2) // 2 bar s1 = "baz" >println(i, s1) // 1 baz //println(j, s2) // ERROR: undefined: j and s2
Так же это может быть связано с AST (Abstract Syntax Tree) — когда оно строится и происходят SSA (Static Single Assignment) оптимизации, к сожалению SSA не работает на всю длину дерева. Как следствие, если у нас слишком длинная функция (примерно дохулион строк) и мы по каким-то причинам не можем её декомпозировать, но можем изолировать какие-то скоупы то, таким образом, мы помогаем SSA произвести оптимизации (если они возможно).
Что такое захват переменной?
Во вложенном скоупе есть возможность обращаться к переменным, объявленных в скоупе выше (но не наоборот). Обращение к переменным из вышестоящего скоупа и есть их захват. Типичной ошибкой является использование значение итератора в цикле:
var out []*int for i := 0; i < 3; i++ < out = append(out, &i) >println(*out[0], *out[1], *out[2]) // 3 3 3
Испраляется путём создания локальной (для скоупа цикла) переменной с копией знаяения итератора:
var out []*int for i := 0; i < 3; i++ < i := i // Copy i into a new variable. out = append(out, &i) >println(*out[0], *out[1], *out[2]) // 0 1 2
Что можно почитать: Using reference to loop iterator variable
Как работает defer ?
Defer является функцией отложенного вызова. Выполняется всегда (даже в случае паники внутри функции вызываемой) после того, как функция завершила своё выполнение но до того, как управление вернётся вызывающей стороне (более того — внутри defer возможен захват переменных, и даже возвращаемого результата). Часто используется для освобождения ресурсов/снятия блокировок. Пример использования:
func main() < println("result =", f()) // f started // defer // defer in defer // result = 25 >func f() (i int) < println("f started") defer func() < recover() defer func() < println("defer in defer"); i += 5 >() println("defer") i = i * 2 >() i = 10 panic("panic is here") >
Когда выполняется ключевое слово defer , оно помещает следующий за ним оператор в список, который будет вызван до возврата функции.
Как работает init ?
В Go есть предопределенная функция init() . Она выделяет фрагмент кода, который должен выполняться перед всеми другими частями пакета. Этот код будет выполняться сразу после импорта пакета.
Также функция init() используется для автоматической регистрации одного пакета в другом (например, так работает подавляющее большинство "драйверов" для различных СУБД, например — go-sql-driver/mysql/driver.go).
Функцию init() можно использовать неоднократно в рамках даже одного файла, выполняться они будут в этом случае в порядке, как их встречает компилятор.
Хотя использование init() и является довольно полезным, но часто оно затрудняет чтение/понимание кода, и (почти) всегда можно обойтись без неё, поэтому необходимость её использования — всегда очень большой вопрос.
Прерывание for/switch или for/select
Что произойдёт в следующем примере, если f() вернёт true ?
Очевидно, будет вызван break . Вот только прерван будет switch , а не цикл for . Простое решение проблемы – использовать именованный (labeled) цикл и вызывать break c этой меткой, как в примере ниже:
loop: for < switch f() < case true: break loop case false: // Do something >>
Сколько можно возвращать значений из функции?
Теоретически, неограниченное количество значений. Так же хочется отметить, что есть правила "де-факто", которых следует придерживаться:
- Последним значением возвращать ошибку, если её возврат подразумевается
- Первым значением возвращать контекст, если он подразумевается
- Хорошим тоном является не возвращать более четырёх значений
- Если функция что-то проверяет и возвращает значение + булевый результат проверки — то результат проверки возвращать последним (пример — os.LookupEnv(key string) (string, bool) )
- Если возвращается ошибка, то остальные значения возвращать нулевыми или nil
Дженерики — это про что?
Дженерики, или обобщения — это средства языка, позволяющего работать с различными типами данных без изменения их описания.
В версии 1.18 появились дженерики (вообще-то они были и ранее, но мы не могли их использовать в своём коде — вспомни функцию make(T type) ), и они позволяют объявлять (описывать) универсальные методы, т.е. в качестве параметров и возвращаемых значений указывать не один тип, а их наборы.
Появились новые ключевые слова:
- any — аналог interface<> , можно использовать в любом месте ( func do(v any) any , var v any , type foo interface < Do() any >)
- comparable — интерфейс, который определяет типы, которые могут быть сравнены с помощью == и != (переменные такого типа создать нельзя — var j comparable будет вызывать ошибку)
И появилась возможность определять интерфейсы, которые можно будет использовать в параметризованных функциях и типах (переменные такого типа создать нельзя — var j Int будет вызывать ошибку):
type Int interface
Если добавить знак ~ перед типами то интерфейсу будут соответствовать и производные типы, например myInt из примера ниже:
type Int interface < ~int | ~int32 | ~int64 >type myInt int
Разработчики golang создали для нас уже готовый набор интерфейсов (пакет constraints ), который очень удобно использовать.
Параметризованные функции
Рассмотрим пример функции, что возвращает максимум из двух переданных значений, причём тип может быть любым:
import "constraints" func Max[T constraints.Ordered](a T, b T) T < if a >b < return a >return b >
Ограничения на используемые типы описываются в квадратных скобочках. В качестве ограничения для типов можно использовать любой интерфейс и особые интерфейсы описанные выше.
Для слайсов и мап был создан набор готовых полезных функций.
Параметризованные типы
import "reflect" type myMap[K comparable, V any] map[K]V func main() < m := myMap[int, string]println(m[5]) // foo println(reflect.TypeOf(m)) // main.myMap[int,string] >
Что можно почитать: Зачем нужны дженерики в Go?, Golang пощупаем дженерики
Память и управление ей
Что такое heap и stack ?
Стек (stack) — это область оперативной памяти, которая создаётся для каждого потока. Он работает в порядке LIFO (Last In, First Out), то есть последний добавленный в стек кусок памяти будет первым в очереди на вывод из стека. Каждый раз, когда функция объявляет новую переменную, она добавляется в стек, а когда эта переменная пропадает из области видимости (например, когда функция заканчивается), она автоматически удаляется из стека. Когда стековая переменная освобождается, эта область памяти становится доступной для других стековых переменных.
Стек быстрый, так как часто привязан к кэшу процессора. Размер стека ограничен, и задаётся при создании потока.
Куча (heap) — это хранилище памяти, также расположенное в ОЗУ, которое допускает динамическое выделение памяти и не работает по принципу стека: это просто склад для ваших переменных. Когда вы выделяете в куче участок памяти для хранения переменной, к ней можно обратиться не только в потоке, но и во всем приложении. Именно так определяются глобальные переменные. По завершении приложения все выделенные участки памяти освобождаются. Размер кучи задаётся при запуске приложения, но, в отличие от стека, он ограничен лишь физически, и это позволяет создавать динамические переменные.
В сравнении со стеком, куча работает медленнее, поскольку переменные разбросаны по памяти, а не сидят на верхушке стека. То что попадает в кучу, живёт там пока не придёт GC.
Но почему стек так быстр? Основных причин две:
- Стеку не нужно иметь сборщик мусора (garbage collector). Как мы уже упоминали, переменные просто создаются и затем вытесняются, когда функция завершается. Не нужно запускать сложный процесс освобождения памяти от неиспользуемых переменных и т.п.
- Стек принадлежит одной горутине, переменные не нужно синхронизировать в сравнении с теми, что находятся в куче. Что также повышает производительность
Где выделяется память под переменную? Можно ли этим управлять?
Прямых инструментов для управления местом, где будет выделена память у нас, к сожалению — нет. Но есть некоторые практики, которые позволяют это понять и использовать эффективно.
Память под переменную может быть выделена в куче (heap) или стеке (stack). Очень приблизительно:
- Стек содержит последовательность переменных для заданной горутины (как только функция завершила работу, переменные вытесняются из стека)
- Куча содержит общие (shared) переменные (глобальные и т.п.)
Давайте рассмотрим простой пример, в котором вы возвращаем значение:
func getFooValue() foo < var result foo // Do something return result >
Здесь переменная result создаётся в текущей горутине. И эта переменная помещается в стек. Как только функция завершает работу, клиент получает копию этой переменной. Исходная переменная вытесняется из стека. Эта переменная всё ещё существует в памяти, до тех пор, пока не будет затёрта другой переменной, но к этой переменной уже нельзя получить доступ.
Теперь тот же пример, но с указателем:
func getFooPointer() *foo < var result foo // Do something return &result >
Переменная result также создаётся текущей горутиной, но клиент получает указатель (копию адреса переменной). Если result вытеснена из стека, клиент функции не сможет получить доступ к переменной.
В подобном сценарии компилятор Go вынужден переместить переменную result туда, где она может быть доступна (shared) – в кучу (heap).
Хотя есть и исключение. Для примера:
func main() < p := &foo<>f(p) >
Поскольку мы вызываем функцию f() в той же горутине, что и функцию main() , переменную p не нужно перемещать. Она просто находится в стеке и вложенная функция f() будет иметь к ней доступ.
В качестве заключения, когда мы создаём функцию — поведением по умолчанию должно быть использование передачи по значению, а не по указателю. Указатель должен быть использован только когда мы действительно хотим переиспользовать данные.
Как работает Garbage Collection ( GC ) в Go?
Garbage Collection — это процесс освобождения места в памяти, которая больше не используется. Стек освобождается быстро и просто (условно-самостоятельно), а вот с кучей имеются некоторые сложности.
В основе работы GC в Go лежит:
- "Трехцветный алгоритм пометки и очистки" (выполняется параллельно с основной программой) — все данные в куче представляются в виде связанного графа, каждая вершина которого (каждый объект, данные) может быть помечена как "белая", "серая", или "чёрная"; данный граф обходится в несколько проходов, все вершины размечаются своими цветами, и "белые" (мусорные) объекты могут быть удалены ("чёрные" — точно нельзя удалять; "серые" — под вопросом, пока не трогать)
- Write Barrier, следящий за тем, чтоб черные объекты не указывали на белые; и "останавливать мир" (Stop The World, STW) для включения или отключения Write Barrier
GC можно вызвать ручками — runtime.GC() , но пользоваться этим нужно с осторожностью (есть риск блокировки вызывающей стороны или всего приложения целиком).
По умолчанию, GC запускается самостоятельно когда размер кучи становится в 2 раза больше (за это отвечает Pacer ; данный коэффициент можно регулировать при сборке с помощью env GOGC ).
Полный цикл работы GC:
- Sweep termination — фаза завершения очистки:
- Stop the World
- Ожидаем пока все горутины достигнут safe-point
- Завершаем очистку ресурсов
- Mark phase — фаза разметки (выполняется конкурентно с основной программой, выделяется на неё ~25% CPU):
- Включаем Write Barrier
- Start the World
- Запускаем сканирование глобальных переменных и стеков
- При сканировании работа горутины приостанавливается (но не происходит полная остановка всей программы)
- Выполняем 3-х цветный алгоритм поиска мусора
- Mark termination — фаза завершения разметки
- Stop the World (не является обязательной, но с ней проце было реализовать)
- Дожидаемся завершения обработки последних задач из очереди
- Очистка кэшей
- Завершаем разметку
- Sweep phase — фаза очистки
- Отключаем Write Barrier
- Start The World
- Очистка ресурсов происходит в фоне
- Не реализован алгоритм поколений (GC Generations)
- Не реализовано уплотнение
- Stop the World (STW), вызываемый аж дважды
- Нет возможности тонкой настройки
Для оптимизации можно:
Какое поведение по умолчанию используется в Go при передаче в функцию?
По умолчанию всегда используется копирование, т.е. передача по значению. Для передачи по указателю необходимо это явно указывать:
func main() < var i = 5 byValue(i) // 5 byPointer(&i) // 5 >func byValue(i int) < println(i) >// передача по значению (копии переменной) func byPointer(i *int) < println(*i) >// передача по указателю
Что можешь рассказать про escape analysis?
Escape analysis — это процесс, который компилятор использует для определения размещения значений, созданных вашей программой.
В частности, компилятор выполняет статический анализ кода, чтобы определить, может ли значение быть помещено в стековый фрейм для функции, которая его строит, или значение должно "сбежать" в кучу. Используется разработчиками для оптимизации кода и аналитики причин возможного замедления.
Команда для запуска escape-анализа: go build -gcflags="-m" (так же можно использовать флаги -N для отключени оптимизаций, -l для отключения "инлайнинга").
Что можно почитать: Языковая механика escape analysis, Escape Analysis in Golang
Сoncurrency (конкурентность)
В данном разделе будут вопросы, относящиеся к параллелизму и конкурентной работе.
Как устроен мьютекс?
Mutex означает MUTual EXclusion (взаимное исключение), и обеспечивает безопасный доступ к общим ресурсам.
Под капотом мьютекса используются функции из пакета atomic ( atomic.CompareAndSwapInt32 и atomic.AddInt32 ), так что можно считать мьютекс надстройкой над atomic . Мьютекс медленнее чем atomic , потому что он блокирует другие горутины на всё время действия блокировки. А в свою очередь atomic быстрее потому как использует атомарные инструкции процессора.
В момент, когда нужно обеспечить защиту доступа — вызываем метод Lock() , а по завершению операции изменения/чтения данных — метод Unlock() .
В чем отличие sync.Mutex от sync.RWMutex ?
Помимо Lock() и Unlock() (у sync.Mutex ), у sync.RWMutex есть отдельные аналогичные методы только для чтения — RLock() и RUnlock() . Если участок в памяти нуждается только в чтении — он использует RLock() , который не заблокирует другие операции чтения, но заблокирует операцию записи и наоборот.
По большому счёту, RWMutex это комбинация из двух мьютексов.
Что такое synс.Map ?
Коротко — предоставляет атомарный доступ к элементам map .
Go, как известно, является языком созданным для написания concurrent программ — программ, который эффективно работают на мультипроцессорных системах. Но тип map не безопасен для параллельного доступа. То есть для чтения, конечно, безопасен — 1000 горутин могут читать из map без опасений, но вот параллельно в неё ещё и писать — уже нет.
Для обеспечения потоко-безопасного доступа к map можно использовать sync.RWMutex , но он имеет проблему производительности при работе на большом количестве ядер процессора (в RWMutex при блокировке на чтение каждая горутина должна обновить поле readerCount — простой счётчик, с помощью atomic.AddInt32() , что проиводит к сбросу кэша для этого адреса памяти для всех ядер, и каждое ядро становится в очередь и ждёт этот сброс и вычитывание из кэша — эта проблема называется cache contention).
sync.Map решает совершенно конкретную проблему cache contention в стандартной библиотеке для таких случаев, когда ключи в map стабильны (не обновляются часто) и происходит намного больше чтений, чем записей.
Пример работы с sync.Map :
var m sync.Map m.Store("one", 1) // запись one, ok := m.Load("one") // чтение fmt.Println(one, ok) // 1 true m.Range(func(k, v interface<>) bool < // итерация эл-ов мапы fmt.Println(k, v) // one 1 return true >) m.Delete("one") // удаление
Что можно почитать: Разбираемся с новым sync.Map в Go 1.9
Какие ещё примитивы синхронизации знаешь?
Как было сказано выше — для синхронизации можно использовать мьютексы. Кроме того из стандартной библиотеки нам доступны:
sync.WaitGroup
Используется для координации в случае, когда программе приходится ждать окончания работы нескольких горутин (эта конструкция похожа на CountDownLatch в Java). Отличный способ дождаться завершения набора одновременных операций. Принцип работы следующий:
var wg sync.WaitGroup wg.Add(1) // увеличиваем счётчик на 1 go func() < fmt.Println("task 1") () wg.Add(1) // увеличиваем счётчик на 1 go func() < fmt.Println("task 2") () wg.Wait() // блокируемся, пока счётчик не будет == 0 // task 2 // task 1 // task 2 done // task 1 done // Total time: 1.00s
sync.Cond
Условная переменная (CONDition variable) полезна, например, если мы хотим разблокировать сразу несколько горутин ( Broadcast ), что не получится сделать с помощью канала. Метод Signal отправляет сообщение самой долго-ожидающей горутине. Пример использования:
var ( c = sync.NewCond(&sync.Mutex<>) wg sync.WaitGroup // нужна только для примера free = true ) wg.Add(1) go func() < defer wg.Done() c.L.Lock() for !free < // проверяем, что ресурс свободен c.Wait() >fmt.Println("work") c.L.Unlock() >() free = false // забрали ресурс, чтобы выполнить с ним работу <-time.After(1 * time.Second) // эмуляция работы free = true // освободили ресурс c.Signal() // оповестили горутину wg.Wait()
sync.Once
Позволяет определить задачу для однократного выполнения за всё время работы программы. Содержит одну-единственную функцию Do , позволяющую передавать другую функцию для однократного применения.
var once sync.Once for i := 0; i < 10; i++ < once.Do(func() < fmt.Println("Hell yeah!") >) > // Hell yeah! (выводится 1 раз вместо 10)
sync.Pool
Используется для уменьшения давления на GC путём повторного использования выделенной памяти (потоко-безопасно). Пул необязательно освободит данные при первом пробуждении GC, но он может освободить их в любой момент. У пула нет возможности определить и установить размер и нет необходимости заботиться о его переполнении.
Что можно почитать: Go sync.Pool
Какие типы каналов существуют?
Если которотко, то синхронные (небуферизированным) и асинхронные (буферизированные), оба работают по принципу FIFO (first in, first out) очереди.
Канал — это объект связи, с помощью которого (чаще всего) горутины обмениваются данными. Потокобезопасен, передаётся "по указателю". Технически это можно представить как конвейер (или трубу), откуда можно считывать и помещать данные. Для создания канала предоставляет ключевое слово chan — создание не буферизированного канала c := make(chan int) , для чтения из канала — data :=
Запись данных в закрытый канал вызовет панику.
Чтение или запись данных в небуферизированный канал блокирует горутину и контроль передается свободной горутине. Через закрытый канал невозможно будет передать или принять данные (проверить открытость канала можно используя val, isOpened := закрыт и отсутствуют данные для чтения из него).
Буферизированный канал создается указанием второго аргумента для make — c := make(chan int, 5) , в этом случае горутина не блокируется до тех пор, пока буфер не будет заполнен. Подобно слайсам, буферизированный канал имеет длину ( len , количество сообщений в очереди, не считанных) и емкость ( cap , размер самого буфера канала):
c := make(chan string, 5) c println(val) > // "foo" // "bar"
При этом ok == true до того момента, пока в канале есть сообщения (вне зависимости от того, открыт он или закрыт), в противном случае ok == false а val будет нулевым значением в зависимости от типа данных канала. При попытке записи в закрытый канал будет паника (авторы языка так сделали "ибо нефиг — канал закрыт значит закрыт").
Используя буферизованный канал и цикл for val := range c < . >мы можем читать с закрытых каналов (поскольку у закрытых каналов данные все еще живут в буфере).
Кроме того, существует синтаксический сахар однонаправленных каналов (улучшает безопасность типов в программe, что, как следствие, порождает меньше ошибок):
Так же можно в сигнатуре принимаемой функции указать однонаправленность канала ( func write(c chan ) — в этом случае функция не сможет из него читать, а сможет только писать или закрыть его.
Читать "одновременно" из нескольких каналов возможно с помощью select (оператор select является блокируемым, за исключением использования default ):
c1, c2 := make(chan string), make(chan string) defer func() < close(c1); close(c2) >() // не забываем прибраться go func(c chan(c1) go func(c chan(c2) for i := 1; ; i++ < select < // блокируемся, пока в один из каналов не попадёт сообщение case val := if i >= 2 < // через 2 итерации выходим (иначе будет deadlock) break >> // channel 1 foo // channel 2 bar // Total execution time: 1.00s
В случае, если в оба канала одновременно придут сообщения (или они уже там были), то case будет выбран случайно (а не по порядку их объявления, как могло бы показаться).
Если ни один из каналов недоступен для взаимодействия, и секция default отсутствует, то текущая горутина переходит в состояние waiting до тех пор, пока какой-то из каналов не станет доступен.
Если в select указан default , то он будет выбран в том случае, если все каналы не имеют сообщений (таким образом select становится не блокируемым).
Под капотом (src/runtime/chan.go) канал представлен структурой:
type hchan struct < qcount uint // количество элементов в буфере dataqsiz uint // размерность буфера buf unsafe.Pointer // указатель на буфер для элементов канала elemsize uint16 // размер одного элемента в канале closed uint32 // флаг, указывающий, закрыт канал или нет elemtype *_type // содержит указатель на тип данных в канале sendx uint // индекс (смещение) в буфере по которому должна производиться запись recvx uint // индекс (смещение) в буфере по которому должно производиться чтение recvq waitq // указатель на связанный список горутин, ожидающих чтения из канала sendq waitq // указатель на связанный список горутин, ожидающих запись в канал lock mutex // мьютекс для безопасного доступа к каналу >
В общем случае, горутина захватывает мьютекс, когда совершает какое-либо действие с каналом, кроме случаев lock-free проверок при неблокирующих вызовах.
Go не выделяет буфер для синхронных (небуферизированных) каналов, поэтому указатель на буфер равен nil и dataqsiz равен нулю. При чтении из канала горутина произведёт некоторые проверки, такие как: закрыт ли канал, буферизирован он или нет, содержит ли гоуртины в send-очереди. Если ожидающих отправки горутин нет — горутина добавит сама себя в recvq и заблокируется. При записи другой горутиной все проверки повторяются снова, и когда она проверяет recvq очередь, она находит ожидающую чтение горутину, удаляет её из очереди, записывает данные в её стек и снимает блокировку. Это единственное место во всём рантайме Go, когда одна горутина пишет напрямую в стек другой горутины.
При создании асинхронного (буферизированного) канала make(chan bool, 1) Go выделяет буфер и устанавливает значение dataqsiz в единицу. Чтобы горутине отправить отправить значение в канал, сперва производятся несколько проверок: пуста ли очередь recvq , пуст ли буфер, достаточно ли места в буфере. Если всё ок, то она просто записывает элемент в буфер, увеличивает значение qcount и продолжает исполнение далее. Когда буфер полон, буферизированный канал будет вести себя точно так же, как синхронный (небуферизированный), тоесть горутина добавит себя в очередь ожидания и заблокируется.
Проверки буфера и очереди реализованы как атомарные операции, и не требуют блокировки мьютекса.
При закрытии канала Go проходит по всем ожидающим на чтение или запись горутинам и разблокирует их. Все получатели получают дефолтные значение переменных того типа данных канала, а все отправители паникуют.
Что можно делать с закрытым каналом?
Из закрытого канала можно читать с помощью for val := range c < . >— вычитает все сообщения что в нём есть, или с помощью:
for < if val, ok := else < break >>
Расскажи про планировщик (горутин)
Goroutine scheduler является перехватывающим задачи (work-stealing) планировщиком, который был введен еще в Go 1.1 Дмитрием Вьюковым вместе с командой Go. Основная его суть заключается в том, что он управляет:
- G (горутинами) — просто горутины Go
- M (машинами aka потоками или тредами) — потоки ОС, которые могут выполнять что-либо или же бездействовать
- P (процессорами) — можно рассматривать как ЦП (физическое ядро); представляет ресурсы, необходимые для выполнения нашего Go кода, такие как планировщик или состояние распределителя памяти
Основная задача планировщика состоит в том, чтобы сопоставить каждую G (код, который мы хотим выполнить) с M (где его выполнять) и P (права и ресурсы для выполнения).
Когда M (поток ОС) прекращает выполнение нашего кода, он возвращает свой P (ЦП) в пул свободных P . Чтобы возобновить выполнение Go кода, он должен повторно заполучить его. Точно так же, когда горутина завершается, объект G (горутина) возвращается в пул свободных G и позже может быть повторно использован для какой-либо другой горутины.
Go запускает столько тредов, сколько доступно процессорных ядер (если вы специально это не перенастраиваете) и распределяет на эти треды сколько угодно горутин которые уже запускает программист. В один момент на одном ядре ЦП может находиться в исполнении только одна грутина, а в очереди исполнения их может быть неограниченное количество.
Треды M во время выполнения могут переходить от одного процессора P к другому. Например, когда тред делает системный вызов, в ответ на который ОС блокирует этот тред (например — чтение какого-то большого файла с диска) — мало того что заблокируется сама горутина, что спровоцировала этот вызов, но и все остальные, что стоят в очереди для этого процессора P . Чтоб этого не происходило — Go отвязывает горутины стоящие в очереди от этого процессора P и переназначает на другие.
Основные типы многозадачности что используются в большинстве ОС это "вытесняющая" (все ресурсы делятся между всеми программами одинаково, всем выделяется одинаковое время выполнения) и "кооперативная" (программы выполняются столько, сколько им нужно, и сами уступают друг-другу место). В Go используется неявная кооперативность:
- Горутина уступает место другис при обращении к вводу-выводу, каналам, вызовам ОС и т.д.
- Может уступить место при вызове любой функции (с некоторой вероятностью произойдет переключение между горутинами)
- Есть явный способ переключить планировщик на другую горутину — вызвать функцию runtime.Gosched() (почти никогда не нужна, но она есть)
Основные принципы планировщика:
- Очередь FIFO (first in — first out) — порядок запуска горутин обуславливается порядом их вызова
- Необходимый минимум тредов — создается не больше тредов чем доступных ядер ЦП
- Захват чужой работы — когда тред простаивает, то он не удаляется рантаймом Go, а будет по возможности "нагружен" работой, взятой из очередей горутин на исполнение с других тредов
- "Неинвазивность" — работа горутин насильно не прерывается
- Очередь FIFO (нет приоритезации и изменения порядка исполнения)
- Отсутствие гарантий времени выполнения (времени запуска горутин)
- Горутины могут перемещаться между тредами, что снижает эффективность кэшей
Что такое горутина?
Горутина (goroutine) — это функция, выполняющаяся конкурентно с другими горутинами в том же адресном пространстве.
Для её запуска достаточно использовать ключевое слово go перед именем вызываемой (или анонимной) функции.
Горутины очень легковесны (~2,6Kb на горутину). Практически все расходы — это создание стека, который очень невелик, хотя при необходимости может расти. Область их применения чаще всего следующая:
- Когда нужна асинхронность (например когда мы работаем с сетью, диском, базой данных, защищенным мьютексом ресурсом и т.п.)
- Когда время выполнения функции достаточно велико и можно получить выигрыш, нагрузив другие ядра
Сама структура горутины занимает порядка 600 байт, но для неё ещё выделяется и её собственный стек, минимальный размер котого составляет 2Kb, который увеличивается и уменьшается по мере необходимости (максимум зависит от архитектуры и составляет 1 ГБ для 64-разрядных систем и 250 МБ для 32-разрядных систем).
Переключение между двумя Горутинами — супер дешевое, O(1) , то есть, не зависит от количества созданных горутин в системе. Всё, что нужно сделать для переключения, это поменять 3 регистра — Program counter , Stack Pointer и DX .
В чем отличия горутин от потоков ОС?
- Каждый поток операционной системы имеет блок памяти фиксированного размера (зачастую до 2 Мбайт) для стека — рабочей области, в которой он хранит локальные переменные вызовов функций, находящиеся в работе или приостановленные на время вызова другой функции. В противоположность этому go-подпрограмма начинает работу с небольшим стеком, обычно около 2 Кбайт. Стек горутины, подобно стеку потока операционной системы, хранит локальные переменные активных и приостановленных функций, но, в отличие от потоков операционной системы, не является фиксированным; при необходимости он может расти и уменьшаться
- Потоки операционной системы планируются в ее ядре, а у go есть собственный планировщик (m:n) мультиплексирующий (раскидывающий) горутинки (m) по потокам (n). Основной плюс — отсутствие оверхеда на переключение контекста
- Планировщик Go использует параметр с именем GOMAXPROCS для определения, сколько потоков операционной системы могут одновременно активно выполнять код Go. Его значение по умолчанию равно количеству процессоров (ядер) компьютера, так что на машине с 8 процессорами (ядрами) планировщик будет планировать код Go для выполнения на 8 потоках одновременно. Спящие или заблокированные в процессе коммуникации go-подпрограммы потоков для себя не требуют. Go-подпрограммы, заблокированные в операции ввода-вывода или в других системных вызовах, или при вызове функций, не являющихся функциями Go, нуждаются в потоке операционной системы, но GOMAXPROCS их не учитывает
- В большинстве операционных систем и языков программирования, поддерживающих многопоточность, текущий поток имеет идентификацию, которая может быть легко получена как обычное значение (обычно — целое число или указатель). У горутин нет идентификации, доступной программисту. Так решено во время проектирования языка, поскольку локальной памятью потока программисты злоупотребляют
Где аллоцируется память для горутин?
Так как горутины являются stackful — то и память для них (их состояние) хранится на стеке. Поэтому, теоретически, если очень постараться и сделать миллиард вложенных вызовов, то можно сделать себе переполнение стека.
Для самих же переменных, что используются внутри горутин память берётся с хипа (ограничены только размером "физического" хипа, т.е. объемом памяти сколько есть на машине).
Как завершить много горутин?
Один из вариантов — это пристрелить main (шутка). Работу одной гороутины в принципе нельзя принудительно остановить из другой. Механизмы их завершения необходимо реализовывать отдельно (учить сами горутины завершаться).
Наиболее часто используются 2 подхода — это использование контекста context.Context :
import ( "context" "time" ) func f(ctx context.Context) < loop: for < select < case > > func main() < ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 3; i++ < go f(ctx) // запускаем 3 горутины ><-time.After(time.Millisecond * 50) cancel() // отменяем контекст, на что горутины должны среагировать выходом <-time.After(time.Millisecond * 60) // do some work // do some work // do some work // break f // break f // break f >
И отдельного канала для уведомлений о необходимости завершения (часто для уведомлений используется пустая структура struct<> , которая ничего не весит):
import ( "time" ) func f(c ) < loop: for < select < case > > func main() < const workersCount = 3 var c = make(chan struct<>, workersCount) for i := 0; i < workersCount; i++ < go f(c) // запускаем 3 горутины ><-time.After(time.Millisecond * 50) for i := 0; i < workersCount; i++ < c <- struct<><> // отправляем 3 сообщения в канал (по одному для каждой горутины) о выходе > // ВООБЩЕ - цикл с отправкой сообщений НЕ является обязательным, и можно просто закрыть канал close(c) <-time.After(time.Millisecond * 60) // do some work // do some work // do some work // break f // break f // break f >
Кейсы использования контекста
Пакет context в Go особенно полезен при взаимодействиях с API и медленными процессами, особенно в production-grade системах. С его помощью можно уведомить горутины о необходимости завершить свою работу, "пошарить" какие-то данные (например, в middleware), или легко организовать работу с таймаутом.
context.WithCancel()
Эта функция создает новый контекст из переданного ей родительского, возвращая первым аргументом новый контекст, а вторым — функцию "отмены контекста" (при её вызове родительский контект "отменен" не будет). Важно — вызывать функцию отмены контекста должна только та функция, которая его создает. При вызове функции отмены сам контекст и все контексты, созданные на основе него получат в ctx.Done() пустую структуру и в ctx.Err() ошибку context.Canceled .
ctx, cancel := context.WithCancel(context.Background()) fmt.Println(ctx.Err()) // nil cancel() fmt.Println( <-ctx.Done()) // <>fmt.Println(ctx.Err().Error()) // context canceled
context.WithDeadline()
Так же создает контекст от родительского, который отменится самостоятельно при наступлении переданной временной отметке, или при вызове функции отмены. Отмена/таймаут затрагивает только сам контекст и его "наследников". ctx.Err() возвращает ошибку context.DeadlineExceeded . Полезно для реализации таймаутов:
ctx, cancel := context.WithDeadline( context.Background(), time.Now().Add(time.Millisecond*100), ) defer cancel() fmt.Println(ctx.Err()) // nil <-time.After(time.Microsecond * 110) fmt.Println(<-ctx.Done()) // <>fmt.Println(ctx.Err().Error()) // context deadline exceeded
context.WithTimeout()
Работает аналогично context.WithDeadline() за исключением того, что принимает в качестве значения таймаута длительность (например — time.Second ):
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
context.WithValue()
Позволяет "пошарить" данные через всё контекстное дерево "ниже". Часто используют чтоб передать таким образом, например, логгер или HTTP запрос в цепочке middleware (но в 9 из 10 случаев так делать не надо, это можно считать антипаттерном). Лучше всего использовать функции для помещения/извлечения данных из контекста (так как "в нём" они храняться как interface<> ):
import ( "context" "log" "os" ) const loggerCtxKey = "logger" // should be unique func PutLogger(ctx context.Context, logger *log.Logger) context.Context < return context.WithValue(ctx, loggerCtxKey, logger) >func GetLogger(ctx context.Context) *log.Logger < return ctx.Value(loggerCtxKey).(*log.Logger) >func f(ctx context.Context) < logger := GetLogger(ctx) logger.Print("inside f") println(logger) >func main() < var ( logger = log.New(os.Stdout, "", 0) ctxWithLogger = PutLogger(context.Background(), logger) ) logger.Printf("main") println(logger) f(ctxWithLogger) // main // 0xc0000101e0 // inside f // 0xc0000101e0 >
Что можно почитать: Разбираемся с пакетом Context в Golang
Как задетектить гонку?
Пишем тесты, и запускаем их с флагом -race (в этом случае рантайм будет в случайном порядке переключаться между горутинами (если не ошибаюсь), и компилятор генерирует дополнительный код, который "журналирует" обращения к памяти). Этот флаг можно использовать как для go test , так и для go run или go build .
Детектор гонки основан на библиотеке времени выполнения (runtime library) C/C++ ThreadSanitizer.
Так же предпочитаю писать тесты, провоцирующие гонку. Код в этом случае будет работать значительно медленнее, но для этапа тестирования это и не так важно. А именно для тестируемой структуры запускаю (например) 100 горутин которые читают и пишут что-то в случайном порядке.
Важно и ещё одно высказывание — "Если race detector обнаруживает состояние гонки, то оно у вас наверняка есть; если же не обнаруживает — то это не означает что его нет".
Тестирование
Для unit-тестирования (aka модульного) используется команда вида go test , которая запускает все функции, что начинаются с префикса Test в файлах, что имеют в своем имени постфикс _test.go — всё довольно просто.
Важно писать сам код так, чтоб его можно было протестировать (например — не забывать про инвертирование зависимостей и использовать интерфейсы там, где они уместны).
TDT, Table-driven tests (табличное тестирование)
Являются более предпочтительным вариантом для тестирования множества однотипных кейсов перед описанием "один кейс — один тест", так как позволяют отделить часть входных данных и ожидаемых данных от всех этапов инициализации и tear-down (не знаю как это будет по-русски). Например, тестируемая функция и её тест могут выглядеть так:
package main func Sum(a, b int) int
package main import "testing" func TestSum(t *testing.T) < for name, tt := range map[string]struct < // ключ мапы - имя теста giveOne, giveSecond int wantResult int >< "1 + 1 = 2": < giveOne: 1, giveSecond: 1, wantResult: 2, >, "140 + 6 = 146": < giveOne: 140, giveSecond: 6, wantResult: 146, >, > < t.Run(name, func(t *testing.T) < // setup here if res := Sum(tt.giveOne, tt.giveSecond); res != tt.wantResult < t.Errorf("Unexpected result. Want %d, got %d", tt.wantResult, res) >// teardown here >) > >
Имя пакета с тестами
Если имя пакета в файле с тестами ( foo_test.go ) указывать с постфиксом _test (например — имя пакета, для которого пишутся тесты foo , а имя пакета указанное в самом файле с тестами для него — foo_test ), то в тестах не будет доступа в не-экспортируемым свойствам, структурам и функциям, таким образом тестирование пакета будет происходить "как извне", и не будет соблазна пытаться использовать что-то приватное, что в пакете содержится. По идее, в одной директории не может находиться 2 и более файлов, имена пакетов в которых отличаются, но *_test является исключением из этого правила.
Более того, этот подход стимулирует тестировать API, а не внутренние механизмы, т.е. относиться к функциональности как к "черному ящику", что очень правильно.
Статические анализаторы (линтеры)
Уже давно на все случаи жизни существует golangci-lint, который является универсальным решением, объединяющим множество линтеров в "одном флаконе". Удобен как для запуска локально, так и на CI.
Ошибка в бенчмарке
Про бенчмарки — иногда встречается кейс с написанием бенчмарка который внутри своего цикла выполняет тестируемую функцию, а результат этого действия никуда не присваивается и не передаётся:
func BenchmarkWrong(b *testing.B) < for i := 0; i < b.N; i++ < ourFunc() >>
Компилятор может принять это во внимание, и будет выполнять её содержимое как inline-последовательность инструкций. После чего, компилятор определит, что вызовы тестируемой функции не имеет никаких побочных эффектов (side-effects), т.е. никак не влияет на среду исполнения. После чего вызов тестируемой функции будет просто удалён. Один из вариантов избежать сценария выше – присваивать результат выполнения функции переменной уровня пакета. Примерно так:
var result uint64 func BenchmarkCorrect(b *testing.B) < var r uint64 for i := 0; i < b.N; i++ < r = ourFunc() >result = r >
Теперь компилятор не будет знать, есть ли у функции side-effect и бенчмарк будет точен.
Что про функциональное тестирование?
Тут всё зависит от того, что мы собираемся тестировать, и тянет на отдельную тему для разговора. Для HTTP посоветовать можно postman и его CLI-версию newman. Ещё как вариант "быстро и просто" — это hurl.
Для за-mock-ивания стороннего HTTP API — jmartin82/mmock или lamoda/gonkey.
Профилирование ( pprof )
Для профилирования "родными" средствами в поставке с Go имеется пакет pprof и одноименная консольная утилита go tool pprof . Причинами необходимости в профилировании могут стать:
- Длительная работа различных частей программы
- Высокое потребление памяти
- Высокое потребление ресурсов процессора
Профилировщик является семплирующим — с какой-то периодичностью мы прерываем работу программы, берем стек-трейс, записываем его куда-то, а в конце, на основе того, как часто в стек-трейсах встречаются разные функции, мы понимаем, какие из них использовали больше ресурсов процессора, а какие меньше. Работа с ним состоит из двух этапов — сбор статистики по работе сервиса, и её визуализация + анализ. Собирать статистику можно добавив вызовы пакета pprof , либо запустив HTTP сервер.
Пример использования pprof
Рассмотрим простой случай, когда у нас есть функция, которая выполняется по какой-то причине очень долго. Обрамим вызовы потенциально-тяжелого кода в startPprof и stopPprof :
Подробности
package main import ( "os" "runtime/pprof" "time" ) func startPprof() *os.File < // вспомогательная функция начала профилирования f, err := os.Create("profile.pprof") if err != nil < panic(err) >if err = pprof.StartCPUProfile(f); err != nil < panic(err) >return f > func stopPprof(f *os.File) < // вспомогательная функция завершения профилирования pprof.StopCPUProfile() if err := f.Close(); err != nil < panic(err) >> func main() < // наша основания функция var ( slice = make([]int, 0) m = make(map[int]int) ) pprofFile := startPprof() // начинаем профилирование // тут начинается какая-то "тяжелая" работа for i := 0; i < 10_000_000; i++ < slice = append(slice, i*i) >for i := 0; i < 10_000_000; i++ < m[i] = i * i >
После компиляции и запуска приложения ( go build -o ./main . && ./main ) в текущей директории появится файл с именем profile.pprof , содержащий профиль работы. "Конвертируем" его в читаемое представление в виде svg изображения с помощью go tool pprof -svg ./profile.pprof (на Linux для этого понадобится установленный пакет graphviz ) и открываем его (имя файла будет в виде profile001.svg ):
Посмотрим на получившийся граф вызовов. Изучая такой граф, в первую очередь нужно обращать внимание на толщину ребер (стрелочек) и на размер узлов графа (квадратиков). На ребрах подписано время — сколько времени данный узел или любой из ниже лежащих узлов находился в стек-трейсе во время профилирования.
В нашем профиле можем заметить, что runtime evacuate_fast64 занимает очень много времени. Связано это с тем, что из мапы данным приходиться эвакуироваться, так как размер мапы очень сильно растёт. Исправляем это (а заодно и слайс) всего в двух строчках:
var ( slice = make([]int, 0, 10_000_000) // заставляем аллоцировать память в слайсе m = make(map[int]int, 10_000_000) // и в мапе заранее )
Повторяем все сделанные ранее операции снова, и видим уже совсем другую картину:
Теперь картина значительно лучше, и следующее место оптимизации (потенциально) это пересмотреть работу с данными, а именно — нужна ли нам работа с мапой в принципе (может заменить её каким-то слайсом), и если нет — то как можно улучшить (оптимизировать) запись в неё.
Так как же профилировщик работает в принципе?
Go runtime просит ОС посылать сигнал ( man setitimer ) с определенной периодичностью и назначает на этот сигнал обработчик. Обработчик берет стек-трейс всех горутин, какую-то дополнительную информацию, записывает ее в буфер и выходит.
Каковы же недостатки данного подхода?
- Каждый сигнал — это изменение контекста, вещь довольно затратная в наше время. В Go сейчас получается получить порядка 100 в секунду. Иногда этого мало
- Для нестандартных сборок, например, с использованием -buildmode=c-archive или -buildmode=c-shared , профайлер работать по умолчанию не будет. Это связано с тем, что сигнал SIGPROF (который посылает ОС) придет в основной поток программы, который не контролируется Go
- Процесс user space , которым является программа на Go, не может получить ядерный стек-трейс. Неоптимальности и проблемы иногда кроются и в ядре
Основное преимущество, конечно, в том, что Go runtime обладает полной информацией о своем внутреннем устройстве. Внешние средства, например, по умолчанию ничего не знают о горутинах. Для них существуют только процессы и треды.
Что можно почитать: Профилирование и оптимизация программ на Go
Компилятор
Компиляция — это процесс преобразования вашего (говно)кода в кашу из машинного кода. Первое понятно тебе, второе — машине.
Из каких этапов состоит компиляция?
cmd/compile содержит основные пакеты Go компилятора. Процесс компиляции может быть логически разделен на четыре фазы:
- Parsing ( cmd/compile/internal/syntax ) — сорец парсится, разбивается на токены, создается синтаксическое дерево
- Type-checking and AST (Abstract Syntax Tree) transformations ( cmd/compile/internal/gc ) — дерево переводится в AST, тут же происходит магия по авто-типизации, проверок интерфейсов этапа компиляции, определяется мертвый код и происходит escape-анализ
- Generic SSA (Static Single Assignment) ( cmd/compile/internal/gc , cmd/compile/internal/ssa ) — AST переводится в SSA (промежуточное представление более низкого уровня), что упрощает реализацию оптимизаций; так же применяются множественные оптимизации этого уровня (тут, например, циклы range переписываются в обычные for ; а copy заменяется перемещением памяти), удаляются ненужные проверки на nil и т.д.
- Generating machine code ( cmd/compile/internal/ssa , cmd/internal/obj ) — универсальные штуки перезаписываются на машинно-зависимые (в зависимости от архитектуры и ОС), после чего над SSA снова выполняются оптимизации, удаляется мертвый код, распределяются регистры, размечается стековый фрейм; после — ассемблер превращает всё это добро в машинный код и записывает объектный файл
Что можно почитать: Введение в компилятор Go
Статическая компиляция/линковка — что это, и в чем особенности?
Линковка (ну или компоновка) последний этап сборки. Статически слинкованный исполняемый файл не зависит от наличия других библиотек в системе во время своей работы.
Для включения статической компиляции/линковки (при этом все внешние библиотеки, от которых зависит исполнение кода будут встроены в итоговый бинарный файл) необходимо использовать переменную окружения при сборке CGO_ENABLED=0 (т.е. CGO_ENABLED=0 go build . ). Полученный бинарный файл можно безбоязненно использовать, например, в docker-образе, основанном на scratch (т.е. не содержащем абсолютно никаких файлов, кристально чистая файловая система).
Однако, это накладывает некоторые ограничения и привносит особенности, которые необходимо помнить:
- C -код будет недоступен, совсем (часть модулей из stdlib Go от него зависят, к слову, но не критичных)
- Не будет использоваться системный DNS-резольвер
- Не будет работать проверка x.509 сертификатов, которая должна работать на MacOS X
И ещё, если итоговый бинарный файл планируется использовать в docker scratch , то так же следует иметь в виду:
- Для осуществления HTTP запросов по протоколу HTTPS вашим приложением, в образ нужно будет поместить корневые SSL/TLS сертификаты /etc/ssl/certs
- Файл временной зоны ( /etc/timezone ) тоже будет необходим, чтоб корректно работать с датой/временем
Что можно почитать: Docker scratch & CGO_ENABLED=0, Кросс-компиляция в Go, Go dns
Какие директивы компилятора знаешь?
Компилятор Go понимает некоторые директивы (пишутся они в виде комментариев, как правило //go:directive ), которые влияют на процесс компиляции (оптимизации, проверок, и т.д.) но не являются частью языка. Вот некоторые из них:
//go:linkname
Указывает компилятору реальное местонахождение функции или переменной. Можно использовать для вызова приватных функций из других пакетов. Требует импортирования пакета unsafe ( import _ "unsafe" ). Формат следующий:
//go:linkname localname [importpath.name]
import ( _ "strings" // for explodeString _ "unsafe" // for go:linkname ) //go:linkname foo main.bar func foo() string func bar() string < return "bar" >//go:linkname explodeString strings.explode func explodeString(s string, n int) []string func main() < println(foo()) // bar println(explodeString("foo", -1)) // [3/3]0xc0000a00f0 >
//go:nosplit
Указывается при объявлении функции, и указывает на то, что вызов функции должен пропускать все обычные проверки на переполнение стека.
//go:norace
Так же указывается при объявлении функции и "выключает" детектор гонки (race detector) для неё.
//go:noinline
Отключает оптимизацию "инлайнига" для функции. Обычно используется отладки компилятора, escape-аналитики или бенчаркинга.
//go:noescape
Тоже "функциональная" директива, смысл которой сводится к тому, что "я доверяю этой функции, и ни один указатель, переданных в качестве аргументов (или возвращенных) этой функции не должен быть помещен в кучу (heap)".
//go:build
Эта директива обеспечивает условную сборку. То есть мы можем "размечать" тегами файлы, и таким образом компилировать только определенные их "наборы" (тегов может быть несколько, а так же можно использовать ! для указания "не"). Часто используется для кодогенерации, указывая какой-то специфичный тег (например ignore — //go:build ignore ) чтоб файл никогда не учавствовал с борке итогового приложения.
В качестве примера создадим 2 файла в одной директории:
// file: main.go //go:build one package main func main()
// file: main2.go //go:build two package main func main()
И соберем с разными значениями -tags для go build или go run (обрати внимение — какой именно файл собирать не указывается, только тег):
$ go run -tags one . one! $ go run -tags two . two!
//go:generate
Позволяет указать какие внешние команды должны вызваться при запуске go generate . Таким образом, мы можем использовать кодогенерацию, к примеру, или выполнять какие-то операции что дожны предшевствовать сборке (например — //go:generate go run gen.go где gen.go это файл, что содержит //go:build ignore т.е. исключён из компиляции, но при этом генерирует для нас какие-то полезные данные и/или целые .go файлы):
package main //go:generate echo "my build process" func main()
$ go generate my build process
//go:embed
Позволяет "встраивать" внешние файлы в Go приложение. Требует импортирования пакета embed ( import _ "embed" ). Поддерживает типы string , []byte и embed.FS . Пример использования:
package main import _ "embed" //go:embed test.txt var hello string func main()
$ echo "hello world" > test.txt $ go run . hello world
Как изменится файл если уменьшить разрядность и частоту дискретизации? 1. останется неизменным
2. уменьшится
3. увеличится
Выбери, какие основные угрозы существуют в Интернете Общение с незнакомыми людьми в чатах или по электронной почте Поиск развлечений (например, игр) в … Интернете Угроза заражения вредоносным программным обеспечением (ПО) Установка нелицензионного программного обеспечения
які об‘єкти табличного процесора Excel ви знаєте? які їх властивості
Введи оценку (0 - остановить ввод): >>> 5 Введи балл (0 — остановить ввод): >>> 4 Введи балл (0 - остановить ввод): >>> 2 В … веди балл (0 — остановить ввод): >>> 3 Введи балл (0 — остановить ввод): >>> 0 Список оценок: [5, 4, 2, 3] Успеваемость: 75.0