Здравствуйте, mrTwister, Вы писали:
T>Если же производительность не устраивает, то ленивость — это один из способов оптимизации, который, как и любая другая оптимизация, усложняет оригинальное (то есть без оптимизаций) решение.
Есть небольшой нюанс, который состоит в том, что решение может требовать бесконечного количества времени/ресурсов. Т. е. изначально решение нерабочее. Но ленивость может сделать его рабочим. Второй нюанс в том, что эта оптимизация выполняется автоматически средой, а не руками.
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, gandjustas, Вы писали:
T>>>То есть ленивость — это по сути есть оптимизация. G>>Наоборот, энергичность — оптимизация.
T>С чего это вдруг? При использовании ленивости мы откладываем вычисления на максимальный срок с надеждой, что эти вычисления вообще никогда не будут выполнены. Это и есть оптимизация.
Снова неверный вывод. Потому что поддержка ленивости очень дорого обходится по памяти. Это тот самый tradeoff, который как раз не ведет к уменьшению потребления ресурсов.
T>Проблема здесь в том, что проявление возможных ошибок также откладывается на максимальный срок, что усложняет тестирование, обнаружение и расследование ошибок.
Вот это я совсем не понял. Если ты неуверен что функция у тебя работает правильно, то напиши тест.
T>>>Если производительность нас устраивает, то ленивость нафиг не нужна. G>>То есть энергичные вычисления не нужны. T>Нет, энергичные вычисления проще и надежнее, но могут быть проблемы с ресурсами.
Не в ресурсах дело, откладывание вычислений небесплатно достается. Надо замыкания таскать итп.
Но кода у тебя есть ленивые вычисления ты вполне можешь принятие решения о необходимости вычисления перенести туда где достаточно данных. Вот именно тут и получается оптимизация, иногда очень значительная.
Хороший пример это Linq.
Вот есть у тебя приложение, где по всем канонам сделана архитектура, разделены слои итп.
И у тебя есть в DAL метод, получающий данные списком, потом эти данные как-то обрабатываются в BL, и отображаются в PL. Но тут внезапно понадобилось в UI добавить разбивку по страницам с сортировкой (чисто для целей UI).
Если тебя данные из базы вытаскиваются энергично, те прямо в DAL, то тебе придется параметры сортировки передавать из PL через BL в DAL, а если у тебя ленивая выборка (IQueryable), то ты просто вызываешь OrderBy, Skip и Take.
Причем учитывая что сортировка выполняется в БД это дает немалый выйгрыш в скорости.
Аналогично в алгоритмах, связанных с поиском решения, можно в одном методе строить все дерево решений, а в другом нагладывать предикаты, отсекая лишние ветви без прохода по всему дереву каждый раз (а оно может быть вообе говоря бесконечным). В энергичном случае надо отбрасывать ветви в момент генерации, а для этого в генератор решений надо передавать все предикаты или параметры для них.
Здравствуйте, gandjustas, Вы писали:
T>>С чего это вдруг? При использовании ленивости мы откладываем вычисления на максимальный срок с надеждой, что эти вычисления вообще никогда не будут выполнены. Это и есть оптимизация. G>Снова неверный вывод. Потому что поддержка ленивости очень дорого обходится по памяти. Это тот самый tradeoff, который как раз не ведет к уменьшению потребления ресурсов.
Ну и что тут такого? Просто в данном случае оптимизация не работает.
T>>Проблема здесь в том, что проявление возможных ошибок также откладывается на максимальный срок, что усложняет тестирование, обнаружение и расследование ошибок. G>Вот это я совсем не понял. Если ты неуверен что функция у тебя работает правильно, то напиши тест.
Может тогда и статическая типизация тоже не нужна, тесты же есть?
Тесты не панацея. Полного покрытия не добьешься, ошибки могут быть интеграционного характера, могут быть связаны с "неидеальностью окружения", ошибка может быть в инсталлере, который недоинсталлировал что-нибудь, или инсталлировал, да не так. Короче говоря, причин которые не ловятся юниттестами может быть миллион.
T>>>>Если производительность нас устраивает, то ленивость нафиг не нужна. G>>>То есть энергичные вычисления не нужны. T>>Нет, энергичные вычисления проще и надежнее, но могут быть проблемы с ресурсами. G>Не в ресурсах дело, откладывание вычислений небесплатно достается. Надо замыкания таскать итп.
Редко когда оптимизация дается бесплатно. Обычно приходится чем-то жертвовать. Оптимизация работает, когда бенефитов больше, чем жертв.
G>Но кода у тебя есть ленивые вычисления ты вполне можешь принятие решения о необходимости вычисления перенести туда где достаточно данных. Вот именно тут и получается оптимизация, иногда очень значительная.
Ну да. Я об этом и говорю.
Здравствуйте, Mystic, Вы писали:
M>Есть небольшой нюанс, который состоит в том, что решение может требовать бесконечного количества времени/ресурсов. Т. е. изначально решение нерабочее. Но ленивость может сделать его рабочим.
Согласен.
M>Второй нюанс в том, что эта оптимизация выполняется автоматически средой, а не руками.
Во-первых, не всегда, во-вторых, даже если оптимизация выполняется автоматически, мы все равно имеем проблемы с поздним обнаружением ошибок.
Здравствуйте, mrTwister, Вы писали:
T>Во-первых, не всегда, во-вторых, даже если оптимизация выполняется автоматически, мы все равно имеем проблемы с поздним обнаружением ошибок.
После оптимизации код становится сложнее, и ошибки исправлять сложнее. Но ничего такого кошмарного не видно. Но откуда проблемы с "поздним обнаружением ошибок"? В любом случае ошибку мы ловим в момент использования неправильных данных, а не в момент генерации неправильных данных
Здравствуйте, Mystic, Вы писали:
M>Здравствуйте, mrTwister, Вы писали:
T>>Во-первых, не всегда, во-вторых, даже если оптимизация выполняется автоматически, мы все равно имеем проблемы с поздним обнаружением ошибок.
M>После оптимизации код становится сложнее, и ошибки исправлять сложнее. Но ничего такого кошмарного не видно. Но откуда проблемы с "поздним обнаружением ошибок"? В любом случае ошибку мы ловим в момент использования неправильных данных, а не в момент генерации неправильных данных
Только этот момент наступает по-разному. Допустим, у нас есть приложение, которое использует набор модулей с динамическим связыванием. Допустим, при деплойменте мы забыли продеплоить один из модулей. В случае с энергичной загрузкой модулей мы узнаем об ошибке сразу же, при старте приложения. В случае с ленивой загрузкой, мы узнаем о проблеме только когда отсутствующий модуль реально понадобится, а это может произойти совсем не скоро, в очень редком сценарии.
Здравствуйте, mrTwister, Вы писали:
T>Только этот момент наступает по-разному. Допустим, у нас есть приложение, которое использует набор модулей с динамическим связыванием. Допустим, при деплойменте мы забыли продеплоить один из модулей. В случае с энергичной загрузкой модулей мы узнаем об ошибке сразу же, при старте приложения. В случае с ленивой загрузкой, мы узнаем о проблеме только когда отсутствующий модуль реально понадобится, а это может произойти совсем не скоро, в очень редком сценарии.
Только при чем тут ленивые вычисления? Тут при вызове функции есть явный побочный эффект. Так можно дойти до того, что при вызов Console.WriteLine вообще не должен вызываться, потому что возвращаемого значения нет (нигде не используется)?
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, gandjustas, Вы писали:
T>>>С чего это вдруг? При использовании ленивости мы откладываем вычисления на максимальный срок с надеждой, что эти вычисления вообще никогда не будут выполнены. Это и есть оптимизация. G>>Снова неверный вывод. Потому что поддержка ленивости очень дорого обходится по памяти. Это тот самый tradeoff, который как раз не ведет к уменьшению потребления ресурсов.
T>Ну и что тут такого? Просто в данном случае оптимизация не работает.
Значит это не оптимизация.
T>>>Проблема здесь в том, что проявление возможных ошибок также откладывается на максимальный срок, что усложняет тестирование, обнаружение и расследование ошибок. G>>Вот это я совсем не понял. Если ты неуверен что функция у тебя работает правильно, то напиши тест.
T>Может тогда и статическая типизация тоже не нужна, тесты же есть? T>Тесты не панацея. Полного покрытия не добьешься, ошибки могут быть интеграционного характера, могут быть связаны с "неидеальностью окружения", ошибка может быть в инсталлере, который недоинсталлировал что-нибудь, или инсталлировал, да не так. Короче говоря, причин которые не ловятся юниттестами может быть миллион.
Круто, что из этого относится к ленивым вычислениям?
Покажешь адекватный код где ленивость с запоминанием усложняет поиск ошибки?
T>>>>>Если производительность нас устраивает, то ленивость нафиг не нужна. G>>>>То есть энергичные вычисления не нужны. T>>>Нет, энергичные вычисления проще и надежнее, но могут быть проблемы с ресурсами. G>>Не в ресурсах дело, откладывание вычислений небесплатно достается. Надо замыкания таскать итп. T>Редко когда оптимизация дается бесплатно. Обычно приходится чем-то жертвовать. Оптимизация работает, когда бенефитов больше, чем жертв.
Еще раз: ленивость не оптимизация, в общем случае она ведет к большему расходу ресурсов.
G>>Но кода у тебя есть ленивые вычисления ты вполне можешь принятие решения о необходимости вычисления перенести туда где достаточно данных. Вот именно тут и получается оптимизация, иногда очень значительная. T>Ну да. Я об этом и говорю.
Нет, ты пытаешься экстраполировать локальный результат на всю ленивость, это неверно.
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, Mystic, Вы писали:
M>>Здравствуйте, mrTwister, Вы писали:
T>>>Во-первых, не всегда, во-вторых, даже если оптимизация выполняется автоматически, мы все равно имеем проблемы с поздним обнаружением ошибок.
M>>После оптимизации код становится сложнее, и ошибки исправлять сложнее. Но ничего такого кошмарного не видно. Но откуда проблемы с "поздним обнаружением ошибок"? В любом случае ошибку мы ловим в момент использования неправильных данных, а не в момент генерации неправильных данных
T>Только этот момент наступает по-разному. Допустим, у нас есть приложение, которое использует набор модулей с динамическим связыванием. Допустим, при деплойменте мы забыли продеплоить один из модулей. В случае с энергичной загрузкой модулей мы узнаем об ошибке сразу же, при старте приложения. В случае с ленивой загрузкой, мы узнаем о проблеме только когда отсутствующий модуль реально понадобится, а это может произойти совсем не скоро, в очень редком сценарии.
Для прикола: .NET грузит сборки в момент первого обращения к типу. Например обращаясь к сборке глубоко внутри метода загрузка произойдет только при вызове этого метода. Соответственно не вызываясь он может не вызвать ошибку в случае отсутствия сборки.
И это при вполне энергичном поведении.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Аноним, Вы писали:
А>>Можно пример в виде кода , не совсем понятно что значит кэш коллекции и то что ее состояние не изменилось ? Откуда оно узнает о том что состояние объекта не менялось ,?
U>Простейший пример:
U>
U>При вызове mapImage кэш проверяет совпадает ли текущее значение второго делегата (ClientBounds.Size) с запомненным, если да, то возвращает запомненное изображение, если нет, то вызывает первый делегат и изображение пересоздает.
Здравствуйте, gandjustas, Вы писали:
G>Снова неверный вывод. Потому что поддержка ленивости очень дорого обходится по памяти. Это тот самый tradeoff, который как раз не ведет к уменьшению потребления ресурсов.
Почему вы так считаете? Ленивость и кеширование концепции не связанные(могут использоваться и вместе и порознь). Как конкретно ленивость увеличивает затраты памяти? Я понимаю что существуют решения, когда затраты памяти действительно возрастают. Но я не вижу закономерности.
Здравствуйте, Mystic, Вы писали:
M>Только при чем тут ленивые вычисления?
Это был пример ленивости при загрузки модулей.
M>Тут при вызове функции есть явный побочный эффект.
Клиенту этот побочный эффект совершенно прозрачен.
M>Так можно дойти до того, что при вызов Console.WriteLine вообще не должен вызываться, потому что возвращаемого значения нет (нигде не используется)?
Пример не к месту, так как Console.WriteLine имеет важный для клиента побочный эффект.
Здравствуйте, gandjustas, Вы писали:
T>>Может тогда и статическая типизация тоже не нужна, тесты же есть? T>>Тесты не панацея. Полного покрытия не добьешься, ошибки могут быть интеграционного характера, могут быть связаны с "неидеальностью окружения", ошибка может быть в инсталлере, который недоинсталлировал что-нибудь, или инсталлировал, да не так. Короче говоря, причин которые не ловятся юниттестами может быть миллион. G>Круто, что из этого относится к ленивым вычислениям?
Все, что угодно.
G>Покажешь адекватный код где ленивость с запоминанием усложняет поиск ошибки?
Представь себе IoC контейнер, описывающий набор singleton объектов. Объекты в контейнере могут создаваться энергично (при создании контейнера), либо лениво (при запросе конкретного объекта). При энергичном создании объектов ошибка в конфигурации контейнера проявится сразу, а при ленивом проявится только потом когда-нибудь.
G>Еще раз: ленивость не оптимизация, в общем случае она ведет к большему расходу ресурсов.
Общего случая нет. Есть, случаи когда ленивость существенно экономит ресурсы, и реже наоборот.
G>Нет, ты пытаешься экстраполировать локальный результат на всю ленивость, это неверно.
Какой локальный результат?
Здравствуйте, gandjustas, Вы писали:
G>Для прикола: .NET грузит сборки в момент первого обращения к типу.
Я знаю. Это и есть оптимизация времени загрузки сборки.
Здравствуйте, mrTwister, Вы писали:
G>>Покажешь адекватный код где ленивость с запоминанием усложняет поиск ошибки? T>Представь себе IoC контейнер, описывающий набор singleton объектов. Объекты в контейнере могут создаваться энергично (при создании контейнера), либо лениво (при запросе конкретного объекта). При энергичном создании объектов ошибка в конфигурации контейнера проявится сразу, а при ленивом проявится только потом когда-нибудь.
Энергично нельзя создавать объекты в контейнере, потому что на момент регистрации типа могут еще не присутствовать все зависимости.
Зато можно валидировать всю конфигурацию, что все зависимости таки будут разрешены.
Пример — низачот, давай дальше.
G>>Еще раз: ленивость не оптимизация, в общем случае она ведет к большему расходу ресурсов. T>Общего случая нет.
Есть.
T>Есть, случаи когда ленивость существенно экономит ресурсы, и реже наоборот.
А есть случаи когда черезмерно ленивая программа падает с OOM.
G>>Нет, ты пытаешься экстраполировать локальный результат на всю ленивость, это неверно. T>Какой локальный результат?
Локальный результат в оптимизации для ленивых рекурсивных структур.
Здравствуйте, Аноним, Вы писали:
U>>При вызове mapImage кэш проверяет совпадает ли текущее значение второго делегата (ClientBounds.Size) с запомненным, если да, то возвращает запомненное изображение, если нет, то вызывает первый делегат и изображение пересоздает.
А>Ну это обычный кэш, ленивости тут помоему нет.
Ленивость есть, т.к. кэш пересчитывает значение не тогда, когда произошло изменение входа, а при обращении за значением.
G>>>Покажешь адекватный код где ленивость с запоминанием усложняет поиск ошибки? T>>Представь себе IoC контейнер, описывающий набор singleton объектов. Объекты в контейнере могут создаваться энергично (при создании контейнера), либо лениво (при запросе конкретного объекта). При энергичном создании объектов ошибка в конфигурации контейнера проявится сразу, а при ленивом проявится только потом когда-нибудь. G>Энергично нельзя создавать объекты в контейнере, потому что на момент регистрации типа могут еще не присутствовать все зависимости.
Не понял, при чем тут момент регистрации типа? В момент регистрации происходит описание типа контейнера, а не экземпляра контейнера. Чувствуешь разницу? Это все равно, что если бы ты писал class A{} и у тебя тут же бы создавался экземляр класса А. Бред. А если надо несколько экземпляров?
G>Зато можно валидировать всю конфигурацию, что все зависимости таки будут разрешены.
Теоретически можно. Но это дополнительный функционал, которым должен обладать контейнер. И что примечательно, ни один из доступных плд .NET контейнеров промышленного качества не обладает подобным функционалом.
Вообще, твое замечание по поводу того, что с поздним обнаружением ошибок надо бороться путем предварительной валидации программы — это конечно классно и офигенно. Это ведь серебряная пуля! Программа стартует и проверяет сама себя на отсутствие всех ошибок. Если находит хотябы одну ошибку, то тут завершается с багрепортом. Тестеры вообще не нужны будут!, их можно будет уволить нафиг (ведь программа сама себя тестирует). И как же я сам до этого не догадался раньше ?!?!
G>Пример — низачот, давай дальше.
Нет, мы с этим еще не закончили
T>>Общего случая нет. G>Есть.
И что это за общий случай?
T>>Есть, случаи когда ленивость существенно экономит ресурсы, и реже наоборот. G>А есть случаи когда черезмерно ленивая программа падает с OOM.
T>>Какой локальный результат? G>Локальный результат в оптимизации для ленивых рекурсивных структур.
С чего ты взял? Учитывая то, в каком разделе мы находимся, я имел ввиду прежде всего применение ленивости для ускорения доступа к СУБД и ускорения старта программы (либо частей программы), как наиболее распространенные случаи использования ленивости в мире .NET
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, gandjustas, Вы писали:
G>>>>Покажешь адекватный код где ленивость с запоминанием усложняет поиск ошибки? T>>>Представь себе IoC контейнер, описывающий набор singleton объектов. Объекты в контейнере могут создаваться энергично (при создании контейнера), либо лениво (при запросе конкретного объекта). При энергичном создании объектов ошибка в конфигурации контейнера проявится сразу, а при ленивом проявится только потом когда-нибудь. G>>Энергично нельзя создавать объекты в контейнере, потому что на момент регистрации типа могут еще не присутствовать все зависимости. T>Не понял, при чем тут момент регистрации типа? В момент регистрации происходит описание типа контейнера, а не экземпляра контейнера. Чувствуешь разницу? Это все равно, что если бы ты писал class A{} и у тебя тут же бы создавался экземляр класса А. Бред. А если надо несколько экземпляров?
Я это знаю.
Ты описал откровенно бажный сценарий для энергичного случая, а в ленивом как раз все ОК.
G>>Пример — низачот, давай дальше. T>Нет, мы с этим еще не закончили
А все равно низачот. Ты описал поведение бажного контейнера, который как раз будет глючить в случае энергичного получения инстансов.
T>>>Общего случая нет. G>>Есть. T>И что это за общий случай?
Это когда все лениво.
T>>>Есть, случаи когда ленивость существенно экономит ресурсы, и реже наоборот. G>>А есть случаи когда черезмерно ленивая программа падает с OOM.
T>>>Какой локальный результат? G>>Локальный результат в оптимизации для ленивых рекурсивных структур. T>С чего ты взял? Учитывая то, в каком разделе мы находимся, я имел ввиду прежде всего применение ленивости для ускорения доступа к СУБД и ускорения старта программы (либо частей программы), как наиболее распространенные случаи использования ленивости в мире .NET
Так с этого и надо было начинать.
Здравствуйте, gandjustas, Вы писали:
T>>Не понял, при чем тут момент регистрации типа? В момент регистрации происходит описание типа контейнера, а не экземпляра контейнера. Чувствуешь разницу? Это все равно, что если бы ты писал class A{} и у тебя тут же бы создавался экземляр класса А. Бред. А если надо несколько экземпляров? G>Я это знаю. G>Ты описал откровенно бажный сценарий для энергичного случая, а в ленивом как раз все ОК.
Что-то тебя не в ту степь понесло. О ленивости или энергичности имеет смысл говорить не при описании типа, а при описании экземпляра. Тут ленивость затрудняет поиск ошибок
G>>>Пример — низачот, давай дальше. T>>Нет, мы с этим еще не закончили G>А все равно низачот. Ты описал поведение бажного контейнера, который как раз будет глючить в случае энергичного получения инстансов.
Не понял, какой еще "бажный" контейнер, ты вообще что имеешь ввиду? В чем "бажность" заключается?
T>>>>Общего случая нет. G>>>Есть. T>>И что это за общий случай? G>Это когда все лениво.
А почему не когда все энергично?
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, gandjustas, Вы писали:
T>>>Не понял, при чем тут момент регистрации типа? В момент регистрации происходит описание типа контейнера, а не экземпляра контейнера. Чувствуешь разницу? Это все равно, что если бы ты писал class A{} и у тебя тут же бы создавался экземляр класса А. Бред. А если надо несколько экземпляров? G>>Я это знаю. G>>Ты описал откровенно бажный сценарий для энергичного случая, а в ленивом как раз все ОК. T>Что-то тебя не в ту степь понесло. О ленивости или энергичности имеет смысл говорить не при описании типа, а при описании экземпляра. Тут ленивость затрудняет поиск ошибок
Это тебя не туда понесло. Ленивость — свойство вычислений, а не типов или экземпляров.
G>>>>Пример — низачот, давай дальше. T>>>Нет, мы с этим еще не закончили G>>А все равно низачот. Ты описал поведение бажного контейнера, который как раз будет глючить в случае энергичного получения инстансов. T>Не понял, какой еще "бажный" контейнер, ты вообще что имеешь ввиду? В чем "бажность" заключается?
В том что в некоторых адекватных сценариях будет падать, именно из-за энергичности вычислений.
T>>>>>Общего случая нет. G>>>>Есть. T>>>И что это за общий случай? G>>Это когда все лениво. T>А почему не когда все энергично?
Так мы же именно ленивость обсуждали.
Я и говорю что в общем случае, когда ленивость везде. Такое ведет к большему в среднем потреблению ресурсов.